diff --git a/modules/backend/assets/css/controls.css b/modules/backend/assets/css/controls.css
index f3889c72b..4139fb7e2 100644
--- a/modules/backend/assets/css/controls.css
+++ b/modules/backend/assets/css/controls.css
@@ -143,6 +143,16 @@ body.slim-container .form-buttons{padding:0 20px 20px}
.select2-drop .select2-results .select2-no-results{padding:7px !important}
.select2-drop .select2-results .select2-highlighted{background:#4da7e8}
.select2-drop .select2-results > li > div{padding:5px 7px 5px}
+[data-control=toolbar] .form-control{display:inline-block;margin-right:15px}
+[data-control=toolbar] .form-control.width-50{width:50px}
+[data-control=toolbar] .form-control.width-100{width:100px}
+[data-control=toolbar] .form-control.width-150{width:150px}
+[data-control=toolbar] input[type=text].form-control,[data-control=toolbar] label{position:relative;top:5px}
+[data-control=toolbar] label{margin-right:7px}
+[data-control=toolbar] label.standalone{margin-right:15px}
+[data-control=toolbar] .select2-container{display:inline-block;width:auto;height:36px}
+[data-control=toolbar] .select2-container .select2-choice{height:34px;line-height:34px}
+[data-control=toolbar] select.form-control.custom-select{display:none}
table.table.data{font-size:12px}
table.table.data thead{background:#ffffff}
table.table.data thead td,table.table.data thead th{border-width:1px;border-top:1px solid #e2e2e2 !important;border-color:#e2e2e2;padding:0;font-weight:normal}
@@ -566,6 +576,7 @@ html.cssanimations .cursor-loading-indicator.hide{display:none}
.control-toolbar .toolbar-item.layout-cell:last-child{padding-right:0}
.control-toolbar .toolbar-item .btn,.control-toolbar .toolbar-item .btn-group,.control-toolbar .toolbar-item .dropdown{white-space:nowrap;float:none;display:inline-block;margin-right:10px}
.control-toolbar .toolbar-item .btn:last-child,.control-toolbar .toolbar-item .btn-group:last-child,.control-toolbar .toolbar-item .dropdown:last-child{margin-right:0}
+.control-toolbar .toolbar-item .btn.standalone,.control-toolbar .toolbar-item .btn-group.standalone,.control-toolbar .toolbar-item .dropdown.standalone{margin-right:15px}
.control-toolbar .toolbar-item .btn-group > .btn,.control-toolbar .toolbar-item .btn-group > .dropdown{margin-right:0;display:inline-block;float:none}
.control-toolbar .toolbar-item .btn-group .dropdown > .btn{margin-right:0;border-bottom-right-radius:0;border-top-right-radius:0}
.control-toolbar .toolbar-item .btn-group .dropdown.last > .btn{border-bottom-right-radius:2px !important;border-top-right-radius:2px !important}
@@ -1179,4 +1190,4 @@ div.control-scrollpad > .scrollpad-scrollbar:hover{opacity:0.7;-webkit-transitio
div.control-scrollpad > .scrollpad-scrollbar[data-visible]{opacity:0.7}
div.control-scrollpad > .scrollpad-scrollbar[data-hidden]{display:none}
div.control-scrollpad[data-direction=horizontal] > .scrollpad-scrollbar{top:auto;left:0;width:auto;height:11px}
-div.control-scrollpad[data-direction=horizontal] > .scrollpad-scrollbar .drag-handle{right:auto;top:2px;height:7px;min-height:0;min-width:10px;width:auto}
+div.control-scrollpad[data-direction=horizontal] > .scrollpad-scrollbar .drag-handle{right:auto;top:2px;height:7px;min-height:0;min-width:10px;width:auto}
\ No newline at end of file
diff --git a/modules/backend/assets/css/october.css b/modules/backend/assets/css/october.css
index ad3d4b1f4..b9a140d17 100644
--- a/modules/backend/assets/css/october.css
+++ b/modules/backend/assets/css/october.css
@@ -1,4 +1,4 @@
-@import "../vendor/select2/select2.css"; html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}
+@import "../vendor/select2/select2.css";@import "../vendor/jcrop/css/jquery.Jcrop.min.css"; html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}
body{margin:0}
article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}
audio,canvas,progress,video{display:inline-block;vertical-align:baseline}
diff --git a/modules/backend/assets/js/october-min.js b/modules/backend/assets/js/october-min.js
index adfe0a18a..90ffec515 100644
--- a/modules/backend/assets/js/october-min.js
+++ b/modules/backend/assets/js/october-min.js
@@ -299,7 +299,9 @@ $(this.options.scrollMarkerContainer).append($('').appendTo(document.body)
+Popup.prototype.setBackdrop=function(val){if(val&&!this.$backdrop){this.$backdrop=$('
')
+if(this.options.zIndex!==null)
+this.$backdrop.css('z-index',this.options.zIndex)
+this.$backdrop.appendTo(document.body)
this.$backdrop.addClass('in')
this.$backdrop.append($(''))}
else if(!val&&this.$backdrop){this.$backdrop.remove()
diff --git a/modules/backend/assets/js/october.dragscroll.js b/modules/backend/assets/js/october.dragscroll.js
index 28f1c8321..e4487c72e 100644
--- a/modules/backend/assets/js/october.dragscroll.js
+++ b/modules/backend/assets/js/october.dragscroll.js
@@ -71,6 +71,9 @@
})
$el.on('mousedown.dragScroll', function(event){
+ if (event.target && event.target.tagName === 'INPUT')
+ return // Don't prevent clicking inputs in the toolbar
+
startDrag(event)
return false
})
@@ -338,6 +341,8 @@
this.scrollClassContainer = null
this.el.removeData('oc.dragScroll')
+ this.el = null
+
BaseProto.dispose.call(this)
}
diff --git a/modules/backend/assets/js/october.popup.js b/modules/backend/assets/js/october.popup.js
index 4efad97d9..937164e47 100644
--- a/modules/backend/assets/js/october.popup.js
+++ b/modules/backend/assets/js/october.popup.js
@@ -84,7 +84,8 @@
extraData: {},
content: null,
size: null,
- adaptiveHeight: false
+ adaptiveHeight: false,
+ zIndex: null
}
Popup.prototype.init = function(){
@@ -175,6 +176,9 @@
if (this.options.adaptiveHeight)
modalDialog.addClass('adaptive-height')
+ if (this.options.zIndex !== null)
+ modal.css('z-index', this.options.zIndex + 20)
+
return modal.append(modalDialog.append(modalContent))
}
@@ -193,7 +197,11 @@
Popup.prototype.setBackdrop = function(val) {
if (val && !this.$backdrop) {
this.$backdrop = $('')
- .appendTo(document.body)
+
+ if (this.options.zIndex !== null)
+ this.$backdrop.css('z-index', this.options.zIndex)
+
+ this.$backdrop.appendTo(document.body)
this.$backdrop.addClass('in')
this.$backdrop.append($(''))
diff --git a/modules/backend/assets/js/vendor/vendor-min.js b/modules/backend/assets/js/vendor/vendor-min.js
index c9ee7682b..1012908e2 100644
--- a/modules/backend/assets/js/vendor/vendor-min.js
+++ b/modules/backend/assets/js/vendor/vendor-min.js
@@ -1869,4 +1869,244 @@ var carry=0;var v=Number.NaN;var prev;do{prev=v;v=d.getTime();ticks.push(v);if(u
tickSize*(unit=="quarter"?3:1));}}else if(unit=="year"){d.setFullYear(d.getFullYear()+tickSize);}else{d.setTime(v+step);}}while(vw)&&w>0){nw=w;nh=(w/$obj.width())*$obj.height();}
+if((nh>h)&&h>0){nh=h;nw=(h/$obj.height())*$obj.width();}
+xscale=$obj.width()/nw;yscale=$obj.height()/nh;$obj.width(nw).height(nh);}
+function unscale(c)
+{return{x:c.x*xscale,y:c.y*yscale,x2:c.x2*xscale,y2:c.y2*yscale,w:c.w*xscale,h:c.h*yscale};}
+function doneSelect(pos)
+{var c=Coords.getFixed();if((c.w>options.minSelect[0])&&(c.h>options.minSelect[1])){Selection.enableHandles();Selection.done();}else{Selection.release();}
+Tracker.setCursor(options.allowSelect?'crosshair':'default');}
+function newSelection(e)
+{if(options.disabled){return false;}
+if(!options.allowSelect){return false;}
+btndown=true;docOffset=getPos($img);Selection.disableHandles();Tracker.setCursor('crosshair');var pos=mouseAbs(e);Coords.setPressed(pos);Selection.update();Tracker.activateHandlers(selectDrag,doneSelect,e.type.substring(0,5)==='touch');KeyManager.watchKeys();e.stopPropagation();e.preventDefault();return false;}
+function selectDrag(pos)
+{Coords.setCurrent(pos);Selection.update();}
+function newTracker()
+{var trk=$('').addClass(cssClass('tracker'));if(is_msie){trk.css({opacity:0,backgroundColor:'white'});}
+return trk;}
+if(typeof(obj)!=='object'){obj=$(obj)[0];}
+if(typeof(opt)!=='object'){opt={};}
+setOptions(opt);var img_css={border:'none',visibility:'visible',margin:0,padding:0,position:'absolute',top:0,left:0};var $origimg=$(obj),img_mode=true;if(obj.tagName=='IMG'){if($origimg[0].width!=0&&$origimg[0].height!=0){$origimg.width($origimg[0].width);$origimg.height($origimg[0].height);}else{var tempImage=new Image();tempImage.src=$origimg[0].src;$origimg.width(tempImage.width);$origimg.height(tempImage.height);}
+var $img=$origimg.clone().removeAttr('id').css(img_css).show();$img.width($origimg.width());$img.height($origimg.height());$origimg.after($img).hide();}else{$img=$origimg.css(img_css).show();img_mode=false;if(options.shade===null){options.shade=true;}}
+presize($img,options.boxWidth,options.boxHeight);var boundx=$img.width(),boundy=$img.height(),$div=$('').width(boundx).height(boundy).addClass(cssClass('holder')).css({position:'relative',backgroundColor:options.bgColor}).insertAfter($origimg).append($img);if(options.addClass){$div.addClass(options.addClass);}
+var $img2=$(''),$img_holder=$('').width('100%').height('100%').css({zIndex:310,position:'absolute',overflow:'hidden'}),$hdl_holder=$('').width('100%').height('100%').css('zIndex',320),$sel=$('').css({position:'absolute',zIndex:600}).dblclick(function(){var c=Coords.getFixed();options.onDblClick.call(api,c);}).insertBefore($img).append($img_holder,$hdl_holder);if(img_mode){$img2=$('
').attr('src',$img.attr('src')).css(img_css).width(boundx).height(boundy),$img_holder.append($img2);}
+if(ie6mode){$sel.css({overflowY:'hidden'});}
+var bound=options.boundary;var $trk=newTracker().width(boundx+(bound*2)).height(boundy+(bound*2)).css({position:'absolute',top:px(-bound),left:px(-bound),zIndex:290}).mousedown(newSelection);var bgcolor=options.bgColor,bgopacity=options.bgOpacity,xlimit,ylimit,xmin,ymin,xscale,yscale,enabled=true,btndown,animating,shift_down;docOffset=getPos($img);var Touch=(function(){function hasTouchSupport(){var support={},events=['touchstart','touchmove','touchend'],el=document.createElement('div'),i;try{for(i=0;ix1+ox){ox-=ox+x1;}
+if(0>y1+oy){oy-=oy+y1;}
+if(boundyboundx){xx=boundx;h=Math.abs((xx-x1)/aspect);yy=rh<0?y1-h:h+y1;}}else{xx=x2;h=rwa/aspect;yy=rh<0?y1-h:y1+h;if(yy<0){yy=0;w=Math.abs((yy-y1)*aspect);xx=rw<0?x1-w:w+x1;}else if(yy>boundy){yy=boundy;w=Math.abs(yy-y1)*aspect;xx=rw<0?x1-w:w+x1;}}
+if(xx>x1){if(xx-x1max_x){xx=x1+max_x;}
+if(yy>y1){yy=y1+(xx-x1)/aspect;}else{yy=y1-(xx-x1)/aspect;}}else if(xxmax_x){xx=x1-max_x;}
+if(yy>y1){yy=y1+(x1-xx)/aspect;}else{yy=y1-(x1-xx)/aspect;}}
+if(xx<0){x1-=xx;xx=0;}else if(xx>boundx){x1-=xx-boundx;xx=boundx;}
+if(yy<0){y1-=yy;yy=0;}else if(yy>boundy){y1-=yy-boundy;yy=boundy;}
+return makeObj(flipCoords(x1,y1,xx,yy));}
+function rebound(p)
+{if(p[0]<0)p[0]=0;if(p[1]<0)p[1]=0;if(p[0]>boundx)p[0]=boundx;if(p[1]>boundy)p[1]=boundy;return[Math.round(p[0]),Math.round(p[1])];}
+function flipCoords(x1,y1,x2,y2)
+{var xa=x1,xb=x2,ya=y1,yb=y2;if(x2xlimit)){x2=(xsize>0)?(x1+xlimit):(x1-xlimit);}
+if(ylimit&&(Math.abs(ysize)>ylimit)){y2=(ysize>0)?(y1+ylimit):(y1-ylimit);}
+if(ymin/yscale&&(Math.abs(ysize)0)?(y1+ymin/yscale):(y1-ymin/yscale);}
+if(xmin/xscale&&(Math.abs(xsize)0)?(x1+xmin/xscale):(x1-xmin/xscale);}
+if(x1<0){x2-=x1;x1-=x1;}
+if(y1<0){y2-=y1;y1-=y1;}
+if(x2<0){x1-=x2;x2-=x2;}
+if(y2<0){y1-=y2;y2-=y2;}
+if(x2>boundx){delta=x2-boundx;x1-=delta;x2-=delta;}
+if(y2>boundy){delta=y2-boundy;y1-=delta;y2-=delta;}
+if(x1>boundx){delta=x1-boundy;y2-=delta;y1-=delta;}
+if(y1>boundy){delta=y1-boundy;y2-=delta;y1-=delta;}
+return makeObj(flipCoords(x1,y1,x2,y2));}
+function makeObj(a)
+{return{x:a[0],y:a[1],x2:a[2],y2:a[3],w:a[2]-a[0],h:a[3]-a[1]};}
+return{flipCoords:flipCoords,setPressed:setPressed,setCurrent:setCurrent,getOffset:getOffset,moveOffset:moveOffset,getCorner:getCorner,getFixed:getFixed};}());var Shade=(function(){var enabled=false,holder=$('').css({position:'absolute',zIndex:240,opacity:0}),shades={top:createShade(),left:createShade().height(boundy),right:createShade().height(boundy),bottom:createShade()};function resizeShades(w,h){shades.left.css({height:px(h)});shades.right.css({height:px(h)});}
+function updateAuto()
+{return updateShade(Coords.getFixed());}
+function updateShade(c)
+{shades.top.css({left:px(c.x),width:px(c.w),height:px(c.y)});shades.bottom.css({top:px(c.y2),left:px(c.x),width:px(c.w),height:px(boundy-c.y2)});shades.right.css({left:px(c.x2),width:px(boundx-c.x2)});shades.left.css({width:px(c.x)});}
+function createShade(){return $('').css({position:'absolute',backgroundColor:options.shadeColor||options.bgColor}).appendTo(holder);}
+function enableShade(){if(!enabled){enabled=true;holder.insertBefore($img);updateAuto();Selection.setBgOpacity(1,0,1);$img2.hide();setBgColor(options.shadeColor||options.bgColor,1);if(Selection.isAwake())
+{setOpacity(options.bgOpacity,1);}
+else setOpacity(1,1);}}
+function setBgColor(color,now){colorChangeMacro(getShades(),color,now);}
+function disableShade(){if(enabled){holder.remove();$img2.show();enabled=false;if(Selection.isAwake()){Selection.setBgOpacity(options.bgOpacity,1,1);}else{Selection.setBgOpacity(1,1,1);Selection.disableHandles();}
+colorChangeMacro($div,0,1);}}
+function setOpacity(opacity,now){if(enabled){if(options.bgFade&&!now){holder.animate({opacity:1-opacity},{queue:false,duration:options.fadeTime});}
+else holder.css({opacity:1-opacity});}}
+function refreshAll(){options.shade?enableShade():disableShade();if(Selection.isAwake())setOpacity(options.bgOpacity);}
+function getShades(){return holder.children();}
+return{update:updateAuto,updateRaw:updateShade,getShades:getShades,setBgColor:setBgColor,enable:enableShade,disable:disableShade,resize:resizeShades,refresh:refreshAll,opacity:setOpacity};}());var Selection=(function(){var awake,hdep=370,borders={},handle={},dragbar={},seehandles=false;function insertBorder(type)
+{var jq=$('').css({position:'absolute',opacity:options.borderOpacity}).addClass(cssClass(type));$img_holder.append(jq);return jq;}
+function dragDiv(ord,zi)
+{var jq=$('').mousedown(createDragger(ord)).css({cursor:ord+'-resize',position:'absolute',zIndex:zi}).addClass('ord-'+ord);if(Touch.support){jq.bind('touchstart.jcrop',Touch.createDragger(ord));}
+$hdl_holder.append(jq);return jq;}
+function insertHandle(ord)
+{var hs=options.handleSize,div=dragDiv(ord,hdep++).css({opacity:options.handleOpacity}).addClass(cssClass('handle'));if(hs){div.width(hs).height(hs);}
+return div;}
+function insertDragbar(ord)
+{return dragDiv(ord,hdep++).addClass('jcrop-dragbar');}
+function createDragbars(li)
+{var i;for(i=0;i').css({position:'fixed',left:'-120px',width:'12px'}).addClass('jcrop-keymgr'),$keywrap=$('').css({position:'absolute',overflow:'hidden'}).append($keymgr);function watchKeys()
+{if(options.keySupport){$keymgr.show();$keymgr.focus();}}
+function onBlur(e)
+{$keymgr.hide();}
+function doNudge(e,x,y)
+{if(options.allowMove){Coords.moveOffset([x,y]);Selection.updateVisible(true);}
+e.preventDefault();e.stopPropagation();}
+function parseKey(e)
+{if(e.ctrlKey||e.metaKey){return true;}
+shift_down=e.shiftKey?true:false;var nudge=shift_down?10:1;switch(e.keyCode){case 37:doNudge(e,-nudge,0);break;case 39:doNudge(e,nudge,0);break;case 38:doNudge(e,0,-nudge);break;case 40:doNudge(e,0,nudge);break;case 27:if(options.allowSelect)Selection.release();break;case 9:return true;}
+return false;}
+if(options.keySupport){$keymgr.keydown(parseKey).blur(onBlur);if(ie6mode||!options.fixedSupport){$keymgr.css({position:'absolute',left:'-20px'});$keywrap.append($keymgr).insertBefore($img);}else{$keymgr.insertBefore($img);}}
+return{watchKeys:watchKeys};}());function setClass(cname)
+{$div.removeClass().addClass(cssClass('holder')).addClass(cname);}
+function animateTo(a,callback)
+{var x1=a[0]/xscale,y1=a[1]/yscale,x2=a[2]/xscale,y2=a[3]/yscale;if(animating){return;}
+var animto=Coords.flipCoords(x1,y1,x2,y2),c=Coords.getFixed(),initcr=[c.x,c.y,c.x2,c.y2],animat=initcr,interv=options.animationDelay,ix1=animto[0]-initcr[0],iy1=animto[1]-initcr[1],ix2=animto[2]-initcr[2],iy2=animto[3]-initcr[3],pcent=0,velocity=options.swingSpeed;x1=animat[0];y1=animat[1];x2=animat[2];y2=animat[3];Selection.animMode(true);var anim_timer;function queueAnimator(){window.setTimeout(animator,interv);}
+var animator=(function(){return function(){pcent+=(100-pcent)/velocity;animat[0]=Math.round(x1+((pcent/100)*ix1));animat[1]=Math.round(y1+((pcent/100)*iy1));animat[2]=Math.round(x2+((pcent/100)*ix2));animat[3]=Math.round(y2+((pcent/100)*iy2));if(pcent>=99.8){pcent=100;}
+if(pcent<100){setSelectRaw(animat);queueAnimator();}else{Selection.done();Selection.animMode(false);if(typeof(callback)==='function'){callback.call(api);}}};}());queueAnimator();}
+function setSelect(rect)
+{setSelectRaw([rect[0]/xscale,rect[1]/yscale,rect[2]/xscale,rect[3]/yscale]);options.onSelect.call(api,unscale(Coords.getFixed()));Selection.enableHandles();}
+function setSelectRaw(l)
+{Coords.setPressed([l[0],l[1]]);Coords.setCurrent([l[2],l[3]]);Selection.update();}
+function tellSelect()
+{return unscale(Coords.getFixed());}
+function tellScaled()
+{return Coords.getFixed();}
+function setOptionsNew(opt)
+{setOptions(opt);interfaceUpdate();}
+function disableCrop()
+{options.disabled=true;Selection.disableHandles();Selection.setCursor('default');Tracker.setCursor('default');}
+function enableCrop()
+{options.disabled=false;interfaceUpdate();}
+function cancelCrop()
+{Selection.done();Tracker.activateHandlers(null,null);}
+function destroy()
+{$(document).unbind('touchstart.jcrop-ios',Touch.fixTouchSupport);$div.remove();$origimg.show();$origimg.css('visibility','visible');$(obj).removeData('Jcrop');}
+function setImage(src,callback)
+{Selection.release();disableCrop();var img=new Image();img.onload=function(){var iw=img.width;var ih=img.height;var bw=options.boxWidth;var bh=options.boxHeight;$img.width(iw).height(ih);$img.attr('src',src);$img2.attr('src',src);presize($img,bw,bh);boundx=$img.width();boundy=$img.height();$img2.width(boundx).height(boundy);$trk.width(boundx+(bound*2)).height(boundy+(bound*2));$div.width(boundx).height(boundy);Shade.resize(boundx,boundy);enableCrop();if(typeof(callback)==='function'){callback.call(api);}};img.src=src;}
+function colorChangeMacro($obj,color,now){var mycolor=color||options.bgColor;if(options.bgFade&&supportsColorFade()&&options.fadeTime&&!now){$obj.animate({backgroundColor:mycolor},{queue:false,duration:options.fadeTime});}else{$obj.css('backgroundColor',mycolor);}}
+function interfaceUpdate(alt)
+{if(options.allowResize){if(alt){Selection.enableOnly();}else{Selection.enableHandles();}}else{Selection.disableHandles();}
+Tracker.setCursor(options.allowSelect?'crosshair':'default');Selection.setCursor(options.allowMove?'move':'default');if(options.hasOwnProperty('trueSize')){xscale=options.trueSize[0]/boundx;yscale=options.trueSize[1]/boundy;}
+if(options.hasOwnProperty('setSelect')){setSelect(options.setSelect);Selection.done();delete(options.setSelect);}
+Shade.refresh();if(options.bgColor!=bgcolor){colorChangeMacro(options.shade?Shade.getShades():$div,options.shade?(options.shadeColor||options.bgColor):options.bgColor);bgcolor=options.bgColor;}
+if(bgopacity!=options.bgOpacity){bgopacity=options.bgOpacity;if(options.shade)Shade.refresh();else Selection.setBgOpacity(bgopacity);}
+xlimit=options.maxSize[0]||0;ylimit=options.maxSize[1]||0;xmin=options.minSize[0]||0;ymin=options.minSize[1]||0;if(options.hasOwnProperty('outerImage')){$img.attr('src',options.outerImage);delete(options.outerImage);}
+Selection.refresh();}
+if(Touch.support)$trk.bind('touchstart.jcrop',Touch.newSelection);$hdl_holder.hide();interfaceUpdate(true);var api={setImage:setImage,animateTo:animateTo,setSelect:setSelect,setOptions:setOptionsNew,tellSelect:tellSelect,tellScaled:tellScaled,setClass:setClass,disable:disableCrop,enable:enableCrop,cancel:cancelCrop,release:Selection.release,destroy:destroy,focus:KeyManager.watchKeys,getBounds:function(){return[boundx*xscale,boundy*yscale];},getWidgetSize:function(){return[boundx,boundy];},getScaleFactor:function(){return[xscale,yscale];},getOptions:function(){return options;},ui:{holder:$div,selection:$sel}};if(is_msie)$div.bind('selectstart',function(){return false;});$origimg.data('Jcrop',api);return api;};$.fn.Jcrop=function(options,callback)
+{var api;this.each(function(){if($(this).data('Jcrop')){if(options==='api')return $(this).data('Jcrop');else $(this).data('Jcrop').setOptions(options);}
+else{if(this.tagName=='IMG')
+$.Jcrop.Loader(this,function(){$(this).css({display:'block',visibility:'hidden'});api=$.Jcrop(this,options);if($.isFunction(callback))callback.call(api);});else{$(this).css({display:'block',visibility:'hidden'});api=$.Jcrop(this,options);if($.isFunction(callback))callback.call(api);}}});return this;};$.Jcrop.Loader=function(imgobj,success,error){var $img=$(imgobj),img=$img[0];function completeCheck(){if(img.complete){$img.unbind('.jcloader');if($.isFunction(success))success.call(img);}
+else window.setTimeout(completeCheck,50);}
+$img.bind('load.jcloader',completeCheck).bind('error.jcloader',function(e){$img.unbind('.jcloader');if($.isFunction(error))error.call(img);});if(img.complete&&$.isFunction(success)){$img.unbind('.jcloader');success.call(img);}};$.Jcrop.defaults={allowSelect:true,allowMove:true,allowResize:true,trackDocument:true,baseClass:'jcrop',addClass:null,bgColor:'black',bgOpacity:0.6,bgFade:false,borderOpacity:0.4,handleOpacity:0.5,handleSize:null,aspectRatio:0,keySupport:true,createHandles:['n','s','e','w','nw','ne','se','sw'],createDragbars:['n','s','e','w'],createBorders:['n','s','e','w'],drawBorders:true,dragEdges:true,fixedSupport:true,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/modules/backend/assets/js/vendor/vendor.js b/modules/backend/assets/js/vendor/vendor.js
index 9d37c98d5..dd5595269 100644
--- a/modules/backend/assets/js/vendor/vendor.js
+++ b/modules/backend/assets/js/vendor/vendor.js
@@ -31,4 +31,6 @@
=require ../../vendor/flot/jquery.flot.resize.js
=require ../../vendor/flot/jquery.flot.time.js
-*/
+=require ../../vendor/jcrop/js/jquery.Jcrop.js
+
+*/
\ No newline at end of file
diff --git a/modules/backend/assets/less/controls/forms.less b/modules/backend/assets/less/controls/forms.less
index f253c60d7..ed1f53574 100644
--- a/modules/backend/assets/less/controls/forms.less
+++ b/modules/backend/assets/less/controls/forms.less
@@ -760,4 +760,55 @@ body.slim-container {
padding: 5px 7px 5px;
}
}
+}
+
+//
+// Controls inside toolbar
+//
+
+[data-control=toolbar] {
+ .form-control {
+ display: inline-block;
+ margin-right: 15px;
+
+ &.width-50 {
+ width: 50px;
+ }
+
+ &.width-100 {
+ width: 100px;
+ }
+
+ &.width-150 {
+ width: 150px;
+ }
+ }
+
+ input[type=text].form-control, label {
+ position: relative;
+ top: 5px;
+ }
+
+ label {
+ margin-right: 7px;
+
+ &.standalone {
+ margin-right: 15px;
+ }
+ }
+
+ .select2-container {
+ display: inline-block;
+ width: auto;
+ height: 36px;
+
+ .select2-choice {
+ height: 34px;
+ line-height: 34px;
+ }
+ }
+
+ select.form-control.custom-select {
+ display: none;
+ }
}
\ No newline at end of file
diff --git a/modules/backend/assets/less/controls/toolbar.less b/modules/backend/assets/less/controls/toolbar.less
index e7fcd7510..c81b3a3eb 100644
--- a/modules/backend/assets/less/controls/toolbar.less
+++ b/modules/backend/assets/less/controls/toolbar.less
@@ -52,6 +52,10 @@
&:last-child {
margin-right: 0;
}
+
+ &.standalone {
+ margin-right: 15px;
+ }
}
.btn-group {
diff --git a/modules/backend/assets/less/october.less b/modules/backend/assets/less/october.less
index ca2803d1c..a4aa565a1 100644
--- a/modules/backend/assets/less/october.less
+++ b/modules/backend/assets/less/october.less
@@ -38,3 +38,4 @@
// Vendor
@import "../vendor/sweet-alert/sweet-alert.less";
@import "../vendor/select2/select2.css";
+@import "../vendor/jcrop/css/jquery.Jcrop.min.css";
diff --git a/modules/backend/assets/vendor/jcrop/MIT-LICENSE.txt b/modules/backend/assets/vendor/jcrop/MIT-LICENSE.txt
new file mode 100755
index 000000000..4f3a0fe43
--- /dev/null
+++ b/modules/backend/assets/vendor/jcrop/MIT-LICENSE.txt
@@ -0,0 +1,22 @@
+Copyright (c) 2011 Tapmodo Interactive LLC,
+ http://github.com/tapmodo/Jcrop
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
diff --git a/modules/backend/assets/vendor/jcrop/OCTOBER-README.md b/modules/backend/assets/vendor/jcrop/OCTOBER-README.md
new file mode 100644
index 000000000..e8b1d4662
--- /dev/null
+++ b/modules/backend/assets/vendor/jcrop/OCTOBER-README.md
@@ -0,0 +1 @@
+There's a hack in line 1090 in jquery.Jcrop.js. The hack prevents DOM element leakage through the event that is not unbound in the destroy() method. --ab Apr 08 2015
\ No newline at end of file
diff --git a/modules/backend/assets/vendor/jcrop/README.md b/modules/backend/assets/vendor/jcrop/README.md
new file mode 100755
index 000000000..c1585fb38
--- /dev/null
+++ b/modules/backend/assets/vendor/jcrop/README.md
@@ -0,0 +1,66 @@
+Jcrop Image Cropping Plugin
+===========================
+
+Jcrop is the quick and easy way to add image cropping functionality to
+your web application. It combines the ease-of-use of a typical jQuery
+plugin with a powerful cross-platform DHTML cropping engine that is
+faithful to familiar desktop graphics applications.
+
+Cross-platform Compatibility
+----------------------------
+
+* Firefox 2+
+* Safari 3+
+* Opera 9.5+
+* Google Chrome 0.2+
+* Internet Explorer 6+
+
+Feature Overview
+----------------
+
+* Attaches unobtrusively to any image
+* Supports aspect ratio locking
+* Supports minSize/maxSize setting
+* Callbacks for selection done, or while moving
+* Keyboard support for nudging selection
+* API features to create interactivity, including animation
+* Support for CSS styling
+* Experimental touch-screen support (iOS, Android, etc)
+
+Contributors
+============
+
+**Special thanks to the following contributors:**
+
+* [Bruno Agutoli](mailto:brunotla1@gmail.com)
+* dhorrigan
+* Phil-B
+* jaymecd
+* all others who have committed their time and effort to help improve Jcrop
+
+MIT License
+===========
+
+**Jcrop is free software under MIT License.**
+
+#### Copyright (c) 2008-2012 Tapmodo Interactive LLC,
http://github.com/tapmodo/Jcrop
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
diff --git a/modules/backend/assets/vendor/jcrop/css/Jcrop.gif b/modules/backend/assets/vendor/jcrop/css/Jcrop.gif
new file mode 100755
index 000000000..72ea7ccb5
Binary files /dev/null and b/modules/backend/assets/vendor/jcrop/css/Jcrop.gif differ
diff --git a/modules/backend/assets/vendor/jcrop/css/jquery.Jcrop.min.css b/modules/backend/assets/vendor/jcrop/css/jquery.Jcrop.min.css
new file mode 100755
index 000000000..edc76b2b3
--- /dev/null
+++ b/modules/backend/assets/vendor/jcrop/css/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/modules/backend/assets/vendor/jcrop/js/jquery.Jcrop.js b/modules/backend/assets/vendor/jcrop/js/jquery.Jcrop.js
new file mode 100755
index 000000000..feed10e92
--- /dev/null
+++ b/modules/backend/assets/vendor/jcrop/js/jquery.Jcrop.js
@@ -0,0 +1,1699 @@
+/**
+ * jquery.Jcrop.js v0.9.12
+ * jQuery Image Cropping Plugin - released under MIT License
+ * Author: Kelly Hallman
+ * http://github.com/tapmodo/Jcrop
+ * Copyright (c) 2008-2013 Tapmodo Interactive LLC {{{
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * }}}
+ */
+
+(function ($) {
+
+ $.Jcrop = function (obj, opt) {
+ var options = $.extend({}, $.Jcrop.defaults),
+ docOffset,
+ _ua = navigator.userAgent.toLowerCase(),
+ is_msie = /msie/.test(_ua),
+ ie6mode = /msie [1-6]\./.test(_ua);
+
+ // Internal Methods {{{
+ function px(n) {
+ return Math.round(n) + 'px';
+ }
+ function cssClass(cl) {
+ return options.baseClass + '-' + cl;
+ }
+ function supportsColorFade() {
+ return $.fx.step.hasOwnProperty('backgroundColor');
+ }
+ function getPos(obj) //{{{
+ {
+ var pos = $(obj).offset();
+ return [pos.left, pos.top];
+ }
+ //}}}
+ function mouseAbs(e) //{{{
+ {
+ return [(e.pageX - docOffset[0]), (e.pageY - docOffset[1])];
+ }
+ //}}}
+ function setOptions(opt) //{{{
+ {
+ if (typeof(opt) !== 'object') opt = {};
+ options = $.extend(options, opt);
+
+ $.each(['onChange','onSelect','onRelease','onDblClick'],function(i,e) {
+ if (typeof(options[e]) !== 'function') options[e] = function () {};
+ });
+ }
+ //}}}
+ function startDragMode(mode, pos, touch) //{{{
+ {
+ docOffset = getPos($img);
+ Tracker.setCursor(mode === 'move' ? mode : mode + '-resize');
+
+ if (mode === 'move') {
+ return Tracker.activateHandlers(createMover(pos), doneSelect, touch);
+ }
+
+ var fc = Coords.getFixed();
+ var opp = oppLockCorner(mode);
+ var opc = Coords.getCorner(oppLockCorner(opp));
+
+ Coords.setPressed(Coords.getCorner(opp));
+ Coords.setCurrent(opc);
+
+ Tracker.activateHandlers(dragmodeHandler(mode, fc), doneSelect, touch);
+ }
+ //}}}
+ function dragmodeHandler(mode, f) //{{{
+ {
+ return function (pos) {
+ if (!options.aspectRatio) {
+ switch (mode) {
+ case 'e':
+ pos[1] = f.y2;
+ break;
+ case 'w':
+ pos[1] = f.y2;
+ break;
+ case 'n':
+ pos[0] = f.x2;
+ break;
+ case 's':
+ pos[0] = f.x2;
+ break;
+ }
+ } else {
+ switch (mode) {
+ case 'e':
+ pos[1] = f.y + 1;
+ break;
+ case 'w':
+ pos[1] = f.y + 1;
+ break;
+ case 'n':
+ pos[0] = f.x + 1;
+ break;
+ case 's':
+ pos[0] = f.x + 1;
+ break;
+ }
+ }
+ Coords.setCurrent(pos);
+ Selection.update();
+ };
+ }
+ //}}}
+ function createMover(pos) //{{{
+ {
+ var lloc = pos;
+ KeyManager.watchKeys();
+
+ return function (pos) {
+ Coords.moveOffset([pos[0] - lloc[0], pos[1] - lloc[1]]);
+ lloc = pos;
+
+ Selection.update();
+ };
+ }
+ //}}}
+ function oppLockCorner(ord) //{{{
+ {
+ switch (ord) {
+ 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 createDragger(ord) //{{{
+ {
+ return function (e) {
+ if (options.disabled) {
+ return false;
+ }
+ if ((ord === 'move') && !options.allowMove) {
+ return false;
+ }
+
+ // Fix position of crop area when dragged the very first time.
+ // Necessary when crop image is in a hidden element when page is loaded.
+ docOffset = getPos($img);
+
+ btndown = true;
+ startDragMode(ord, mouseAbs(e));
+ e.stopPropagation();
+ e.preventDefault();
+ return false;
+ };
+ }
+ //}}}
+ function presize($obj, w, h) //{{{
+ {
+ var nw = $obj.width(),
+ nh = $obj.height();
+ if ((nw > w) && w > 0) {
+ nw = w;
+ nh = (w / $obj.width()) * $obj.height();
+ }
+ if ((nh > h) && h > 0) {
+ nh = h;
+ nw = (h / $obj.height()) * $obj.width();
+ }
+ xscale = $obj.width() / nw;
+ yscale = $obj.height() / nh;
+ $obj.width(nw).height(nh);
+ }
+ //}}}
+ function unscale(c) //{{{
+ {
+ return {
+ x: c.x * xscale,
+ y: c.y * yscale,
+ x2: c.x2 * xscale,
+ y2: c.y2 * yscale,
+ w: c.w * xscale,
+ h: c.h * yscale
+ };
+ }
+ //}}}
+ function doneSelect(pos) //{{{
+ {
+ var c = Coords.getFixed();
+ if ((c.w > options.minSelect[0]) && (c.h > options.minSelect[1])) {
+ Selection.enableHandles();
+ Selection.done();
+ } else {
+ Selection.release();
+ }
+ Tracker.setCursor(options.allowSelect ? 'crosshair' : 'default');
+ }
+ //}}}
+ function newSelection(e) //{{{
+ {
+ if (options.disabled) {
+ return false;
+ }
+ if (!options.allowSelect) {
+ return false;
+ }
+ btndown = true;
+ docOffset = getPos($img);
+ Selection.disableHandles();
+ Tracker.setCursor('crosshair');
+ var pos = mouseAbs(e);
+ Coords.setPressed(pos);
+ Selection.update();
+ Tracker.activateHandlers(selectDrag, doneSelect, e.type.substring(0,5)==='touch');
+ KeyManager.watchKeys();
+
+ e.stopPropagation();
+ e.preventDefault();
+ return false;
+ }
+ //}}}
+ function selectDrag(pos) //{{{
+ {
+ Coords.setCurrent(pos);
+ Selection.update();
+ }
+ //}}}
+ function newTracker() //{{{
+ {
+ var trk = $('').addClass(cssClass('tracker'));
+ if (is_msie) {
+ trk.css({
+ opacity: 0,
+ backgroundColor: 'white'
+ });
+ }
+ return trk;
+ }
+ //}}}
+
+ // }}}
+ // Initialization {{{
+ // Sanitize some options {{{
+ if (typeof(obj) !== 'object') {
+ obj = $(obj)[0];
+ }
+ if (typeof(opt) !== 'object') {
+ opt = {};
+ }
+ // }}}
+ setOptions(opt);
+ // Initialize some jQuery objects {{{
+ // The values are SET on the image(s) for the interface
+ // If the original image has any of these set, they will be reset
+ // However, if you destroy() the Jcrop instance the original image's
+ // character in the DOM will be as you left it.
+ var img_css = {
+ border: 'none',
+ visibility: 'visible',
+ margin: 0,
+ padding: 0,
+ position: 'absolute',
+ top: 0,
+ left: 0
+ };
+
+ var $origimg = $(obj),
+ img_mode = true;
+
+ if (obj.tagName == 'IMG') {
+ // Fix size of crop image.
+ // Necessary when crop image is within a hidden element when page is loaded.
+ if ($origimg[0].width != 0 && $origimg[0].height != 0) {
+ // Obtain dimensions from contained img element.
+ $origimg.width($origimg[0].width);
+ $origimg.height($origimg[0].height);
+ } else {
+ // Obtain dimensions from temporary image in case the original is not loaded yet (e.g. IE 7.0).
+ var tempImage = new Image();
+ tempImage.src = $origimg[0].src;
+ $origimg.width(tempImage.width);
+ $origimg.height(tempImage.height);
+ }
+
+ var $img = $origimg.clone().removeAttr('id').css(img_css).show();
+
+ $img.width($origimg.width());
+ $img.height($origimg.height());
+ $origimg.after($img).hide();
+
+ } else {
+ $img = $origimg.css(img_css).show();
+ img_mode = false;
+ if (options.shade === null) { options.shade = true; }
+ }
+
+ presize($img, options.boxWidth, options.boxHeight);
+
+ var boundx = $img.width(),
+ boundy = $img.height(),
+
+
+ $div = $('').width(boundx).height(boundy).addClass(cssClass('holder')).css({
+ position: 'relative',
+ backgroundColor: options.bgColor
+ }).insertAfter($origimg).append($img);
+
+ if (options.addClass) {
+ $div.addClass(options.addClass);
+ }
+
+ var $img2 = $(''),
+
+ $img_holder = $('')
+ .width('100%').height('100%').css({
+ zIndex: 310,
+ position: 'absolute',
+ overflow: 'hidden'
+ }),
+
+ $hdl_holder = $('')
+ .width('100%').height('100%').css('zIndex', 320),
+
+ $sel = $('')
+ .css({
+ position: 'absolute',
+ zIndex: 600
+ }).dblclick(function(){
+ var c = Coords.getFixed();
+ options.onDblClick.call(api,c);
+ }).insertBefore($img).append($img_holder, $hdl_holder);
+
+ if (img_mode) {
+
+ $img2 = $('
')
+ .attr('src', $img.attr('src')).css(img_css).width(boundx).height(boundy),
+
+ $img_holder.append($img2);
+
+ }
+
+ if (ie6mode) {
+ $sel.css({
+ overflowY: 'hidden'
+ });
+ }
+
+ var bound = options.boundary;
+ var $trk = newTracker().width(boundx + (bound * 2)).height(boundy + (bound * 2)).css({
+ position: 'absolute',
+ top: px(-bound),
+ left: px(-bound),
+ zIndex: 290
+ }).mousedown(newSelection);
+
+ /* }}} */
+ // Set more variables {{{
+ var bgcolor = options.bgColor,
+ bgopacity = options.bgOpacity,
+ xlimit, ylimit, xmin, ymin, xscale, yscale, enabled = true,
+ btndown, animating, shift_down;
+
+ docOffset = getPos($img);
+ // }}}
+ // }}}
+ // Internal Modules {{{
+ // Touch Module {{{
+ var Touch = (function () {
+ // Touch support detection function adapted (under MIT License)
+ // from code by Jeffrey Sambells - http://github.com/iamamused/
+ function hasTouchSupport() {
+ var support = {}, events = ['touchstart', 'touchmove', 'touchend'],
+ el = document.createElement('div'), i;
+
+ try {
+ for(i=0; i x1 + ox) {
+ ox -= ox + x1;
+ }
+ if (0 > y1 + oy) {
+ oy -= oy + y1;
+ }
+
+ if (boundy < y2 + oy) {
+ oy += boundy - (y2 + oy);
+ }
+ if (boundx < x2 + ox) {
+ ox += boundx - (x2 + ox);
+ }
+
+ x1 += ox;
+ x2 += ox;
+ y1 += oy;
+ y2 += oy;
+ }
+ //}}}
+ function getCorner(ord) //{{{
+ {
+ var c = getFixed();
+ switch (ord) {
+ case 'ne':
+ return [c.x2, c.y];
+ case 'nw':
+ return [c.x, c.y];
+ case 'se':
+ return [c.x2, c.y2];
+ case 'sw':
+ return [c.x, c.y2];
+ }
+ }
+ //}}}
+ function getFixed() //{{{
+ {
+ if (!options.aspectRatio) {
+ return getRect();
+ }
+ // This function could use some optimization I think...
+ var aspect = options.aspectRatio,
+ min_x = options.minSize[0] / xscale,
+
+
+ //min_y = options.minSize[1]/yscale,
+ max_x = options.maxSize[0] / xscale,
+ max_y = options.maxSize[1] / yscale,
+ rw = x2 - x1,
+ rh = y2 - y1,
+ rwa = Math.abs(rw),
+ rha = Math.abs(rh),
+ real_ratio = rwa / rha,
+ xx, yy, w, h;
+
+ if (max_x === 0) {
+ max_x = boundx * 10;
+ }
+ if (max_y === 0) {
+ max_y = boundy * 10;
+ }
+ if (real_ratio < aspect) {
+ yy = y2;
+ w = rha * aspect;
+ xx = rw < 0 ? x1 - w : w + x1;
+
+ if (xx < 0) {
+ xx = 0;
+ h = Math.abs((xx - x1) / aspect);
+ yy = rh < 0 ? y1 - h : h + y1;
+ } else if (xx > boundx) {
+ xx = boundx;
+ h = Math.abs((xx - x1) / aspect);
+ yy = rh < 0 ? y1 - h : h + y1;
+ }
+ } else {
+ xx = x2;
+ h = rwa / aspect;
+ yy = rh < 0 ? y1 - h : y1 + h;
+ if (yy < 0) {
+ yy = 0;
+ w = Math.abs((yy - y1) * aspect);
+ xx = rw < 0 ? x1 - w : w + x1;
+ } else if (yy > boundy) {
+ yy = boundy;
+ w = Math.abs(yy - y1) * aspect;
+ xx = rw < 0 ? x1 - w : w + x1;
+ }
+ }
+
+ // Magic %-)
+ if (xx > x1) { // right side
+ if (xx - x1 < min_x) {
+ xx = x1 + min_x;
+ } else if (xx - x1 > max_x) {
+ xx = x1 + max_x;
+ }
+ if (yy > y1) {
+ yy = y1 + (xx - x1) / aspect;
+ } else {
+ yy = y1 - (xx - x1) / aspect;
+ }
+ } else if (xx < x1) { // left side
+ if (x1 - xx < min_x) {
+ xx = x1 - min_x;
+ } else if (x1 - xx > max_x) {
+ xx = x1 - max_x;
+ }
+ if (yy > y1) {
+ yy = y1 + (x1 - xx) / aspect;
+ } else {
+ yy = y1 - (x1 - xx) / aspect;
+ }
+ }
+
+ if (xx < 0) {
+ x1 -= xx;
+ xx = 0;
+ } else if (xx > boundx) {
+ x1 -= xx - boundx;
+ xx = boundx;
+ }
+
+ if (yy < 0) {
+ y1 -= yy;
+ yy = 0;
+ } else if (yy > boundy) {
+ y1 -= yy - boundy;
+ yy = boundy;
+ }
+
+ return makeObj(flipCoords(x1, y1, xx, yy));
+ }
+ //}}}
+ function rebound(p) //{{{
+ {
+ if (p[0] < 0) p[0] = 0;
+ if (p[1] < 0) p[1] = 0;
+
+ if (p[0] > boundx) p[0] = boundx;
+ if (p[1] > boundy) p[1] = boundy;
+
+ return [Math.round(p[0]), Math.round(p[1])];
+ }
+ //}}}
+ function flipCoords(x1, y1, x2, y2) //{{{
+ {
+ var xa = x1,
+ xb = x2,
+ ya = y1,
+ yb = y2;
+ if (x2 < x1) {
+ xa = x2;
+ xb = x1;
+ }
+ if (y2 < y1) {
+ ya = y2;
+ yb = y1;
+ }
+ return [xa, ya, xb, yb];
+ }
+ //}}}
+ function getRect() //{{{
+ {
+ var xsize = x2 - x1,
+ ysize = y2 - y1,
+ delta;
+
+ if (xlimit && (Math.abs(xsize) > xlimit)) {
+ x2 = (xsize > 0) ? (x1 + xlimit) : (x1 - xlimit);
+ }
+ if (ylimit && (Math.abs(ysize) > ylimit)) {
+ y2 = (ysize > 0) ? (y1 + ylimit) : (y1 - ylimit);
+ }
+
+ if (ymin / yscale && (Math.abs(ysize) < ymin / yscale)) {
+ y2 = (ysize > 0) ? (y1 + ymin / yscale) : (y1 - ymin / yscale);
+ }
+ if (xmin / xscale && (Math.abs(xsize) < xmin / xscale)) {
+ x2 = (xsize > 0) ? (x1 + xmin / xscale) : (x1 - xmin / xscale);
+ }
+
+ if (x1 < 0) {
+ x2 -= x1;
+ x1 -= x1;
+ }
+ if (y1 < 0) {
+ y2 -= y1;
+ y1 -= y1;
+ }
+ if (x2 < 0) {
+ x1 -= x2;
+ x2 -= x2;
+ }
+ if (y2 < 0) {
+ y1 -= y2;
+ y2 -= y2;
+ }
+ if (x2 > boundx) {
+ delta = x2 - boundx;
+ x1 -= delta;
+ x2 -= delta;
+ }
+ if (y2 > boundy) {
+ delta = y2 - boundy;
+ y1 -= delta;
+ y2 -= delta;
+ }
+ if (x1 > boundx) {
+ delta = x1 - boundy;
+ y2 -= delta;
+ y1 -= delta;
+ }
+ if (y1 > boundy) {
+ delta = y1 - boundy;
+ y2 -= delta;
+ y1 -= delta;
+ }
+
+ return makeObj(flipCoords(x1, y1, x2, y2));
+ }
+ //}}}
+ function makeObj(a) //{{{
+ {
+ return {
+ x: a[0],
+ y: a[1],
+ x2: a[2],
+ y2: a[3],
+ w: a[2] - a[0],
+ h: a[3] - a[1]
+ };
+ }
+ //}}}
+
+ return {
+ flipCoords: flipCoords,
+ setPressed: setPressed,
+ setCurrent: setCurrent,
+ getOffset: getOffset,
+ moveOffset: moveOffset,
+ getCorner: getCorner,
+ getFixed: getFixed
+ };
+ }());
+
+ //}}}
+ // Shade Module {{{
+ var Shade = (function() {
+ var enabled = false,
+ holder = $('').css({
+ position: 'absolute',
+ zIndex: 240,
+ opacity: 0
+ }),
+ shades = {
+ top: createShade(),
+ left: createShade().height(boundy),
+ right: createShade().height(boundy),
+ bottom: createShade()
+ };
+
+ function resizeShades(w,h) {
+ shades.left.css({ height: px(h) });
+ shades.right.css({ height: px(h) });
+ }
+ function updateAuto()
+ {
+ return updateShade(Coords.getFixed());
+ }
+ function updateShade(c)
+ {
+ shades.top.css({
+ left: px(c.x),
+ width: px(c.w),
+ height: px(c.y)
+ });
+ shades.bottom.css({
+ top: px(c.y2),
+ left: px(c.x),
+ width: px(c.w),
+ height: px(boundy-c.y2)
+ });
+ shades.right.css({
+ left: px(c.x2),
+ width: px(boundx-c.x2)
+ });
+ shades.left.css({
+ width: px(c.x)
+ });
+ }
+ function createShade() {
+ return $('').css({
+ position: 'absolute',
+ backgroundColor: options.shadeColor||options.bgColor
+ }).appendTo(holder);
+ }
+ function enableShade() {
+ if (!enabled) {
+ enabled = true;
+ holder.insertBefore($img);
+ updateAuto();
+ Selection.setBgOpacity(1,0,1);
+ $img2.hide();
+
+ setBgColor(options.shadeColor||options.bgColor,1);
+ if (Selection.isAwake())
+ {
+ setOpacity(options.bgOpacity,1);
+ }
+ else setOpacity(1,1);
+ }
+ }
+ function setBgColor(color,now) {
+ colorChangeMacro(getShades(),color,now);
+ }
+ function disableShade() {
+ if (enabled) {
+ holder.remove();
+ $img2.show();
+ enabled = false;
+ if (Selection.isAwake()) {
+ Selection.setBgOpacity(options.bgOpacity,1,1);
+ } else {
+ Selection.setBgOpacity(1,1,1);
+ Selection.disableHandles();
+ }
+ colorChangeMacro($div,0,1);
+ }
+ }
+ function setOpacity(opacity,now) {
+ if (enabled) {
+ if (options.bgFade && !now) {
+ holder.animate({
+ opacity: 1-opacity
+ },{
+ queue: false,
+ duration: options.fadeTime
+ });
+ }
+ else holder.css({opacity:1-opacity});
+ }
+ }
+ function refreshAll() {
+ options.shade ? enableShade() : disableShade();
+ if (Selection.isAwake()) setOpacity(options.bgOpacity);
+ }
+ function getShades() {
+ return holder.children();
+ }
+
+ return {
+ update: updateAuto,
+ updateRaw: updateShade,
+ getShades: getShades,
+ setBgColor: setBgColor,
+ enable: enableShade,
+ disable: disableShade,
+ resize: resizeShades,
+ refresh: refreshAll,
+ opacity: setOpacity
+ };
+ }());
+ // }}}
+ // Selection Module {{{
+ var Selection = (function () {
+ var awake,
+ hdep = 370,
+ borders = {},
+ handle = {},
+ dragbar = {},
+ seehandles = false;
+
+ // Private Methods
+ function insertBorder(type) //{{{
+ {
+ var jq = $('').css({
+ position: 'absolute',
+ opacity: options.borderOpacity
+ }).addClass(cssClass(type));
+ $img_holder.append(jq);
+ return jq;
+ }
+ //}}}
+ function dragDiv(ord, zi) //{{{
+ {
+ var jq = $('').mousedown(createDragger(ord)).css({
+ cursor: ord + '-resize',
+ position: 'absolute',
+ zIndex: zi
+ }).addClass('ord-'+ord);
+
+ if (Touch.support) {
+ jq.bind('touchstart.jcrop', Touch.createDragger(ord));
+ }
+
+ $hdl_holder.append(jq);
+ return jq;
+ }
+ //}}}
+ function insertHandle(ord) //{{{
+ {
+ var hs = options.handleSize,
+
+ div = dragDiv(ord, hdep++).css({
+ opacity: options.handleOpacity
+ }).addClass(cssClass('handle'));
+
+ if (hs) { div.width(hs).height(hs); }
+
+ return div;
+ }
+ //}}}
+ function insertDragbar(ord) //{{{
+ {
+ return dragDiv(ord, hdep++).addClass('jcrop-dragbar');
+ }
+ //}}}
+ function createDragbars(li) //{{{
+ {
+ var i;
+ for (i = 0; i < li.length; i++) {
+ dragbar[li[i]] = insertDragbar(li[i]);
+ }
+ }
+ //}}}
+ function createBorders(li) //{{{
+ {
+ var cl,i;
+ for (i = 0; i < li.length; i++) {
+ switch(li[i]){
+ case'n': cl='hline'; break;
+ case's': cl='hline bottom'; break;
+ case'e': cl='vline right'; break;
+ case'w': cl='vline'; break;
+ }
+ borders[li[i]] = insertBorder(cl);
+ }
+ }
+ //}}}
+ function createHandles(li) //{{{
+ {
+ var i;
+ for (i = 0; i < li.length; i++) {
+ handle[li[i]] = insertHandle(li[i]);
+ }
+ }
+ //}}}
+ function moveto(x, y) //{{{
+ {
+ if (!options.shade) {
+ $img2.css({
+ top: px(-y),
+ left: px(-x)
+ });
+ }
+ $sel.css({
+ top: px(y),
+ left: px(x)
+ });
+ }
+ //}}}
+ function resize(w, h) //{{{
+ {
+ $sel.width(Math.round(w)).height(Math.round(h));
+ }
+ //}}}
+ function refresh() //{{{
+ {
+ var c = Coords.getFixed();
+
+ Coords.setPressed([c.x, c.y]);
+ Coords.setCurrent([c.x2, c.y2]);
+
+ updateVisible();
+ }
+ //}}}
+
+ // Internal Methods
+ function updateVisible(select) //{{{
+ {
+ if (awake) {
+ return update(select);
+ }
+ }
+ //}}}
+ function update(select) //{{{
+ {
+ var c = Coords.getFixed();
+
+ resize(c.w, c.h);
+ moveto(c.x, c.y);
+ if (options.shade) Shade.updateRaw(c);
+
+ awake || show();
+
+ if (select) {
+ options.onSelect.call(api, unscale(c));
+ } else {
+ options.onChange.call(api, unscale(c));
+ }
+ }
+ //}}}
+ function setBgOpacity(opacity,force,now) //{{{
+ {
+ if (!awake && !force) return;
+ if (options.bgFade && !now) {
+ $img.animate({
+ opacity: opacity
+ },{
+ queue: false,
+ duration: options.fadeTime
+ });
+ } else {
+ $img.css('opacity', opacity);
+ }
+ }
+ //}}}
+ function show() //{{{
+ {
+ $sel.show();
+
+ if (options.shade) Shade.opacity(bgopacity);
+ else setBgOpacity(bgopacity,true);
+
+ awake = true;
+ }
+ //}}}
+ function release() //{{{
+ {
+ disableHandles();
+ $sel.hide();
+
+ if (options.shade) Shade.opacity(1);
+ else setBgOpacity(1);
+
+ awake = false;
+ options.onRelease.call(api);
+ }
+ //}}}
+ function showHandles() //{{{
+ {
+ if (seehandles) {
+ $hdl_holder.show();
+ }
+ }
+ //}}}
+ function enableHandles() //{{{
+ {
+ seehandles = true;
+ if (options.allowResize) {
+ $hdl_holder.show();
+ return true;
+ }
+ }
+ //}}}
+ function disableHandles() //{{{
+ {
+ seehandles = false;
+ $hdl_holder.hide();
+ }
+ //}}}
+ function animMode(v) //{{{
+ {
+ if (v) {
+ animating = true;
+ disableHandles();
+ } else {
+ animating = false;
+ enableHandles();
+ }
+ }
+ //}}}
+ function done() //{{{
+ {
+ animMode(false);
+ refresh();
+ }
+ //}}}
+ // Insert draggable elements {{{
+ // Insert border divs for outline
+
+ if (options.dragEdges && $.isArray(options.createDragbars))
+ createDragbars(options.createDragbars);
+
+ if ($.isArray(options.createHandles))
+ createHandles(options.createHandles);
+
+ if (options.drawBorders && $.isArray(options.createBorders))
+ createBorders(options.createBorders);
+
+ //}}}
+
+ // This is a hack for iOS5 to support drag/move touch functionality
+ // Hack OctoberCMS - the event handler was moved to the Touch module.
+ // The closure used before was handling a reference to the target object,
+ // preventing it from removing from DOM after the control is destroyed.
+ $(document).bind('touchstart.jcrop-ios', Touch.fixTouchSupport);
+
+ var $track = newTracker().mousedown(createDragger('move')).css({
+ cursor: 'move',
+ position: 'absolute',
+ zIndex: 360
+ });
+
+ if (Touch.support) {
+ $track.bind('touchstart.jcrop', Touch.createDragger('move'));
+ }
+
+ $img_holder.append($track);
+ disableHandles();
+
+ return {
+ updateVisible: updateVisible,
+ update: update,
+ release: release,
+ refresh: refresh,
+ isAwake: function () {
+ return awake;
+ },
+ setCursor: function (cursor) {
+ $track.css('cursor', cursor);
+ },
+ enableHandles: enableHandles,
+ enableOnly: function () {
+ seehandles = true;
+ },
+ showHandles: showHandles,
+ disableHandles: disableHandles,
+ animMode: animMode,
+ setBgOpacity: setBgOpacity,
+ done: done
+ };
+ }());
+
+ //}}}
+ // Tracker Module {{{
+ var Tracker = (function () {
+ var onMove = function () {},
+ onDone = function () {},
+ trackDoc = options.trackDocument;
+
+ function toFront(touch) //{{{
+ {
+ $trk.css({
+ zIndex: 450
+ });
+
+ if (touch)
+ $(document)
+ .bind('touchmove.jcrop', trackTouchMove)
+ .bind('touchend.jcrop', trackTouchEnd);
+
+ else if (trackDoc)
+ $(document)
+ .bind('mousemove.jcrop',trackMove)
+ .bind('mouseup.jcrop',trackUp);
+ }
+ //}}}
+ function toBack() //{{{
+ {
+ $trk.css({
+ zIndex: 290
+ });
+ $(document).unbind('.jcrop');
+ }
+ //}}}
+ function trackMove(e) //{{{
+ {
+ onMove(mouseAbs(e));
+ return false;
+ }
+ //}}}
+ function trackUp(e) //{{{
+ {
+ e.preventDefault();
+ e.stopPropagation();
+
+ if (btndown) {
+ btndown = false;
+
+ onDone(mouseAbs(e));
+
+ if (Selection.isAwake()) {
+ options.onSelect.call(api, unscale(Coords.getFixed()));
+ }
+
+ toBack();
+ onMove = function () {};
+ onDone = function () {};
+ }
+
+ return false;
+ }
+ //}}}
+ function activateHandlers(move, done, touch) //{{{
+ {
+ btndown = true;
+ onMove = move;
+ onDone = done;
+ toFront(touch);
+ return false;
+ }
+ //}}}
+ function trackTouchMove(e) //{{{
+ {
+ onMove(mouseAbs(Touch.cfilter(e)));
+ return false;
+ }
+ //}}}
+ function trackTouchEnd(e) //{{{
+ {
+ return trackUp(Touch.cfilter(e));
+ }
+ //}}}
+ function setCursor(t) //{{{
+ {
+ $trk.css('cursor', t);
+ }
+ //}}}
+
+ if (!trackDoc) {
+ $trk.mousemove(trackMove).mouseup(trackUp).mouseout(trackUp);
+ }
+
+ $img.before($trk);
+ return {
+ activateHandlers: activateHandlers,
+ setCursor: setCursor
+ };
+ }());
+ //}}}
+ // KeyManager Module {{{
+ var KeyManager = (function () {
+ var $keymgr = $('').css({
+ position: 'fixed',
+ left: '-120px',
+ width: '12px'
+ }).addClass('jcrop-keymgr'),
+
+ $keywrap = $('').css({
+ position: 'absolute',
+ overflow: 'hidden'
+ }).append($keymgr);
+
+ function watchKeys() //{{{
+ {
+ if (options.keySupport) {
+ $keymgr.show();
+ $keymgr.focus();
+ }
+ }
+ //}}}
+ function onBlur(e) //{{{
+ {
+ $keymgr.hide();
+ }
+ //}}}
+ function doNudge(e, x, y) //{{{
+ {
+ if (options.allowMove) {
+ Coords.moveOffset([x, y]);
+ Selection.updateVisible(true);
+ }
+ e.preventDefault();
+ e.stopPropagation();
+ }
+ //}}}
+ function parseKey(e) //{{{
+ {
+ if (e.ctrlKey || e.metaKey) {
+ return true;
+ }
+ shift_down = e.shiftKey ? true : false;
+ var nudge = shift_down ? 10 : 1;
+
+ switch (e.keyCode) {
+ case 37:
+ doNudge(e, -nudge, 0);
+ break;
+ case 39:
+ doNudge(e, nudge, 0);
+ break;
+ case 38:
+ doNudge(e, 0, -nudge);
+ break;
+ case 40:
+ doNudge(e, 0, nudge);
+ break;
+ case 27:
+ if (options.allowSelect) Selection.release();
+ break;
+ case 9:
+ return true;
+ }
+
+ return false;
+ }
+ //}}}
+
+ if (options.keySupport) {
+ $keymgr.keydown(parseKey).blur(onBlur);
+ if (ie6mode || !options.fixedSupport) {
+ $keymgr.css({
+ position: 'absolute',
+ left: '-20px'
+ });
+ $keywrap.append($keymgr).insertBefore($img);
+ } else {
+ $keymgr.insertBefore($img);
+ }
+ }
+
+
+ return {
+ watchKeys: watchKeys
+ };
+ }());
+ //}}}
+ // }}}
+ // API methods {{{
+ function setClass(cname) //{{{
+ {
+ $div.removeClass().addClass(cssClass('holder')).addClass(cname);
+ }
+ //}}}
+ function animateTo(a, callback) //{{{
+ {
+ var x1 = a[0] / xscale,
+ y1 = a[1] / yscale,
+ x2 = a[2] / xscale,
+ y2 = a[3] / yscale;
+
+ if (animating) {
+ return;
+ }
+
+ var animto = Coords.flipCoords(x1, y1, x2, y2),
+ c = Coords.getFixed(),
+ initcr = [c.x, c.y, c.x2, c.y2],
+ animat = initcr,
+ interv = options.animationDelay,
+ ix1 = animto[0] - initcr[0],
+ iy1 = animto[1] - initcr[1],
+ ix2 = animto[2] - initcr[2],
+ iy2 = animto[3] - initcr[3],
+ pcent = 0,
+ velocity = options.swingSpeed;
+
+ x1 = animat[0];
+ y1 = animat[1];
+ x2 = animat[2];
+ y2 = animat[3];
+
+ Selection.animMode(true);
+ var anim_timer;
+
+ function queueAnimator() {
+ window.setTimeout(animator, interv);
+ }
+ var animator = (function () {
+ return function () {
+ pcent += (100 - pcent) / velocity;
+
+ animat[0] = Math.round(x1 + ((pcent / 100) * ix1));
+ animat[1] = Math.round(y1 + ((pcent / 100) * iy1));
+ animat[2] = Math.round(x2 + ((pcent / 100) * ix2));
+ animat[3] = Math.round(y2 + ((pcent / 100) * iy2));
+
+ if (pcent >= 99.8) {
+ pcent = 100;
+ }
+ if (pcent < 100) {
+ setSelectRaw(animat);
+ queueAnimator();
+ } else {
+ Selection.done();
+ Selection.animMode(false);
+ if (typeof(callback) === 'function') {
+ callback.call(api);
+ }
+ }
+ };
+ }());
+ queueAnimator();
+ }
+ //}}}
+ function setSelect(rect) //{{{
+ {
+ setSelectRaw([rect[0] / xscale, rect[1] / yscale, rect[2] / xscale, rect[3] / yscale]);
+ options.onSelect.call(api, unscale(Coords.getFixed()));
+ Selection.enableHandles();
+ }
+ //}}}
+ function setSelectRaw(l) //{{{
+ {
+ Coords.setPressed([l[0], l[1]]);
+ Coords.setCurrent([l[2], l[3]]);
+ Selection.update();
+ }
+ //}}}
+ function tellSelect() //{{{
+ {
+ return unscale(Coords.getFixed());
+ }
+ //}}}
+ function tellScaled() //{{{
+ {
+ return Coords.getFixed();
+ }
+ //}}}
+ function setOptionsNew(opt) //{{{
+ {
+ setOptions(opt);
+ interfaceUpdate();
+ }
+ //}}}
+ function disableCrop() //{{{
+ {
+ options.disabled = true;
+ Selection.disableHandles();
+ Selection.setCursor('default');
+ Tracker.setCursor('default');
+ }
+ //}}}
+ function enableCrop() //{{{
+ {
+ options.disabled = false;
+ interfaceUpdate();
+ }
+ //}}}
+ function cancelCrop() //{{{
+ {
+ Selection.done();
+ Tracker.activateHandlers(null, null);
+ }
+ //}}}
+ function destroy() //{{{
+ {
+ $(document).unbind('touchstart.jcrop-ios', Touch.fixTouchSupport);
+ $div.remove();
+ $origimg.show();
+ $origimg.css('visibility','visible');
+ $(obj).removeData('Jcrop');
+ }
+ //}}}
+ function setImage(src, callback) //{{{
+ {
+ Selection.release();
+ disableCrop();
+ var img = new Image();
+ img.onload = function () {
+ var iw = img.width;
+ var ih = img.height;
+ var bw = options.boxWidth;
+ var bh = options.boxHeight;
+ $img.width(iw).height(ih);
+ $img.attr('src', src);
+ $img2.attr('src', src);
+ presize($img, bw, bh);
+ boundx = $img.width();
+ boundy = $img.height();
+ $img2.width(boundx).height(boundy);
+ $trk.width(boundx + (bound * 2)).height(boundy + (bound * 2));
+ $div.width(boundx).height(boundy);
+ Shade.resize(boundx,boundy);
+ enableCrop();
+
+ if (typeof(callback) === 'function') {
+ callback.call(api);
+ }
+ };
+ img.src = src;
+ }
+ //}}}
+ function colorChangeMacro($obj,color,now) {
+ var mycolor = color || options.bgColor;
+ if (options.bgFade && supportsColorFade() && options.fadeTime && !now) {
+ $obj.animate({
+ backgroundColor: mycolor
+ }, {
+ queue: false,
+ duration: options.fadeTime
+ });
+ } else {
+ $obj.css('backgroundColor', mycolor);
+ }
+ }
+ function interfaceUpdate(alt) //{{{
+ // This method tweaks the interface based on options object.
+ // Called when options are changed and at end of initialization.
+ {
+ if (options.allowResize) {
+ if (alt) {
+ Selection.enableOnly();
+ } else {
+ Selection.enableHandles();
+ }
+ } else {
+ Selection.disableHandles();
+ }
+
+ Tracker.setCursor(options.allowSelect ? 'crosshair' : 'default');
+ Selection.setCursor(options.allowMove ? 'move' : 'default');
+
+ if (options.hasOwnProperty('trueSize')) {
+ xscale = options.trueSize[0] / boundx;
+ yscale = options.trueSize[1] / boundy;
+ }
+
+ if (options.hasOwnProperty('setSelect')) {
+ setSelect(options.setSelect);
+ Selection.done();
+ delete(options.setSelect);
+ }
+
+ Shade.refresh();
+
+ if (options.bgColor != bgcolor) {
+ colorChangeMacro(
+ options.shade? Shade.getShades(): $div,
+ options.shade?
+ (options.shadeColor || options.bgColor):
+ options.bgColor
+ );
+ bgcolor = options.bgColor;
+ }
+
+ if (bgopacity != options.bgOpacity) {
+ bgopacity = options.bgOpacity;
+ if (options.shade) Shade.refresh();
+ else Selection.setBgOpacity(bgopacity);
+ }
+
+ xlimit = options.maxSize[0] || 0;
+ ylimit = options.maxSize[1] || 0;
+ xmin = options.minSize[0] || 0;
+ ymin = options.minSize[1] || 0;
+
+ if (options.hasOwnProperty('outerImage')) {
+ $img.attr('src', options.outerImage);
+ delete(options.outerImage);
+ }
+
+ Selection.refresh();
+ }
+ //}}}
+ //}}}
+
+ if (Touch.support) $trk.bind('touchstart.jcrop', Touch.newSelection);
+
+ $hdl_holder.hide();
+ interfaceUpdate(true);
+
+ var api = {
+ setImage: setImage,
+ animateTo: animateTo,
+ setSelect: setSelect,
+ setOptions: setOptionsNew,
+ tellSelect: tellSelect,
+ tellScaled: tellScaled,
+ setClass: setClass,
+
+ disable: disableCrop,
+ enable: enableCrop,
+ cancel: cancelCrop,
+ release: Selection.release,
+ destroy: destroy,
+
+ focus: KeyManager.watchKeys,
+
+ getBounds: function () {
+ return [boundx * xscale, boundy * yscale];
+ },
+ getWidgetSize: function () {
+ return [boundx, boundy];
+ },
+ getScaleFactor: function () {
+ return [xscale, yscale];
+ },
+ getOptions: function() {
+ // careful: internal values are returned
+ return options;
+ },
+
+ ui: {
+ holder: $div,
+ selection: $sel
+ }
+ };
+
+ if (is_msie) $div.bind('selectstart', function () { return false; });
+
+ $origimg.data('Jcrop', api);
+ return api;
+ };
+ $.fn.Jcrop = function (options, callback) //{{{
+ {
+ var api;
+ // Iterate over each object, attach Jcrop
+ this.each(function () {
+ // If we've already attached to this object
+ if ($(this).data('Jcrop')) {
+ // The API can be requested this way (undocumented)
+ if (options === 'api') return $(this).data('Jcrop');
+ // Otherwise, we just reset the options...
+ else $(this).data('Jcrop').setOptions(options);
+ }
+ // If we haven't been attached, preload and attach
+ else {
+ if (this.tagName == 'IMG')
+ $.Jcrop.Loader(this,function(){
+ $(this).css({display:'block',visibility:'hidden'});
+ api = $.Jcrop(this, options);
+ if ($.isFunction(callback)) callback.call(api);
+ });
+ else {
+ $(this).css({display:'block',visibility:'hidden'});
+ api = $.Jcrop(this, options);
+ if ($.isFunction(callback)) callback.call(api);
+ }
+ }
+ });
+
+ // Return "this" so the object is chainable (jQuery-style)
+ return this;
+ };
+ //}}}
+ // $.Jcrop.Loader - basic image loader {{{
+
+ $.Jcrop.Loader = function(imgobj,success,error){
+ var $img = $(imgobj), img = $img[0];
+
+ function completeCheck(){
+ if (img.complete) {
+ $img.unbind('.jcloader');
+ if ($.isFunction(success)) success.call(img);
+ }
+ else window.setTimeout(completeCheck,50);
+ }
+
+ $img
+ .bind('load.jcloader',completeCheck)
+ .bind('error.jcloader',function(e){
+ $img.unbind('.jcloader');
+ if ($.isFunction(error)) error.call(img);
+ });
+
+ if (img.complete && $.isFunction(success)){
+ $img.unbind('.jcloader');
+ success.call(img);
+ }
+ };
+
+ //}}}
+ // Global Defaults {{{
+ $.Jcrop.defaults = {
+
+ // Basic Settings
+ allowSelect: true,
+ allowMove: true,
+ allowResize: true,
+
+ trackDocument: true,
+
+ // Styling Options
+ baseClass: 'jcrop',
+ addClass: null,
+ bgColor: 'black',
+ bgOpacity: 0.6,
+ bgFade: false,
+ borderOpacity: 0.4,
+ handleOpacity: 0.5,
+ handleSize: null,
+
+ aspectRatio: 0,
+ keySupport: true,
+ createHandles: ['n','s','e','w','nw','ne','se','sw'],
+ createDragbars: ['n','s','e','w'],
+ createBorders: ['n','s','e','w'],
+ drawBorders: true,
+ dragEdges: true,
+ fixedSupport: true,
+ 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],
+
+ // Callbacks / Event Handlers
+ onChange: function () {},
+ onSelect: function () {},
+ onDblClick: function () {},
+ onRelease: function () {}
+ };
+
+ // }}}
+}(jQuery));
diff --git a/modules/backend/classes/Controller.php b/modules/backend/classes/Controller.php
index 7638d61a8..a09dc5d1e 100644
--- a/modules/backend/classes/Controller.php
+++ b/modules/backend/classes/Controller.php
@@ -15,6 +15,7 @@ use Exception;
use BackendAuth;
use Backend\Models\UserPreferences;
use Backend\Models\BackendPreferences;
+use Cms\Widgets\MediaManager;
use System\Classes\ErrorHandler;
use October\Rain\Exception\AjaxException;
use October\Rain\Exception\SystemException;
@@ -147,6 +148,10 @@ class Controller extends Extendable
$this->viewPath = $this->configPath = $this->guessViewPath();
parent::__construct();
+
+ // Media Manager widget is available on all back-end pages
+ $manager = new MediaManager($this, 'ocmediamanager');
+ $manager->bindToController();
}
/**
diff --git a/modules/backend/formwidgets/richeditor/assets/js/build-min.js b/modules/backend/formwidgets/richeditor/assets/js/build-min.js
index 1f834fc40..beab831de 100644
--- a/modules/backend/formwidgets/richeditor/assets/js/build-min.js
+++ b/modules/backend/formwidgets/richeditor/assets/js/build-min.js
@@ -1877,7 +1877,7 @@ self.$form.trigger('change')
if(self.$dataLocker)
self.$dataLocker.val(self.syncBefore(this.$editor.html()))}}
if(this.options.fullpage){redactorOptions.fullpage=true}
-redactorOptions.plugins=['cleanup','fullscreen','figure','quote','table','mediamanager','image']
+redactorOptions.plugins=['cleanup','fullscreen','figure','quote','table','mediamanager']
redactorOptions.buttons=['formatting','bold','italic','unorderedlist','orderedlist','link','horizontalrule','html'],this.$textarea.redactor(redactorOptions)}
RichEditor.prototype.build=function(redactor){this.updateLayout()
$(window).resize($.proxy(this.updateLayout,this))
diff --git a/modules/backend/formwidgets/richeditor/assets/js/richeditor.js b/modules/backend/formwidgets/richeditor/assets/js/richeditor.js
index a3e2bffe4..1371c3fc0 100644
--- a/modules/backend/formwidgets/richeditor/assets/js/richeditor.js
+++ b/modules/backend/formwidgets/richeditor/assets/js/richeditor.js
@@ -81,7 +81,7 @@
// redactorOptions.plugins = ['cleanup', 'fullscreen', 'figure', 'image', 'quote', 'table']
// redactorOptions.buttons = ['formatting', 'bold', 'italic', 'unorderedlist', 'orderedlist', 'link', 'horizontalrule', 'html'],
- redactorOptions.plugins = ['cleanup', 'fullscreen', 'figure', 'quote', 'table', 'mediamanager', 'image']
+ redactorOptions.plugins = ['cleanup', 'fullscreen', 'figure', 'quote', 'table', 'mediamanager']
redactorOptions.buttons = ['formatting', 'bold', 'italic', 'unorderedlist', 'orderedlist', 'link', 'horizontalrule', 'html'],
this.$textarea.redactor(redactorOptions)
diff --git a/modules/backend/layouts/_head.htm b/modules/backend/layouts/_head.htm
index 834be8ad1..315e2f2d4 100644
--- a/modules/backend/layouts/_head.htm
+++ b/modules/backend/layouts/_head.htm
@@ -17,6 +17,11 @@
+
+
+
+
+
= $this->makeAssets() ?>
= Block::placeholder('head') ?>
= $this->makeLayoutPartial('custom_styles') ?>
\ No newline at end of file
diff --git a/modules/cms/classes/MediaLibrary.php b/modules/cms/classes/MediaLibrary.php
index b3f80a74c..c75a4aef1 100644
--- a/modules/cms/classes/MediaLibrary.php
+++ b/modules/cms/classes/MediaLibrary.php
@@ -4,6 +4,7 @@ use ApplicationException;
use SystemException;
use Config;
use Storage;
+use Lang;
use Cache;
use Str;
@@ -385,6 +386,18 @@ class MediaLibrary
return $path;
}
+ /**
+ * Returns a public file URL.
+ * @param string $path Specifies the file path relative the the Library root.
+ * @return string
+ */
+ public function getPathUrl($path)
+ {
+ $path = $this->validatePath($path);
+
+ return $this->storagePath.$path;
+ }
+
/**
* Returns a file or folder path with the prefixed storage folder.
* @param string $path Specifies a path to process.
diff --git a/modules/cms/lang/en/lang.php b/modules/cms/lang/en/lang.php
index 36198aad9..81b823006 100644
--- a/modules/cms/lang/en/lang.php
+++ b/modules/cms/lang/en/lang.php
@@ -276,6 +276,17 @@ return [
'insert' => 'Insert',
'crop_and_insert' => 'Crop & Insert',
'select_single_image' => 'Please select a single image.',
- 'selection_not_image' => 'The selected item is not an image.'
+ 'selection_not_image' => 'The selected item is not an image.',
+ 'restore' => 'Undo all changes',
+ 'resize' => 'Resize...',
+ 'selection_mode_normal' => 'Normal',
+ 'selection_mode_fixed_ratio' => 'Fixed ratio',
+ 'selection_mode_fixed_size' => 'Fixed size',
+ 'height' => 'Height',
+ 'width' => 'Width',
+ 'selection_mode' => 'Selection mode',
+ 'resize_image' => 'Resize image',
+ 'image_size' => 'Image size:',
+ 'selected_size' => 'Selected:'
]
];
diff --git a/modules/cms/widgets/MediaManager.php b/modules/cms/widgets/MediaManager.php
index e9c4fc1c2..52bc7755a 100644
--- a/modules/cms/widgets/MediaManager.php
+++ b/modules/cms/widgets/MediaManager.php
@@ -4,6 +4,7 @@ use URL;
use Str;
use Lang;
use File;
+use Form;
use Input;
use Request;
use Response;
@@ -24,10 +25,15 @@ use October\Rain\Database\Attach\Resizer;
class MediaManager extends WidgetBase
{
const FOLDER_ROOT = '/';
+
const VIEW_MODE_GRID = 'grid';
const VIEW_MODE_LIST = 'list';
const VIEW_MODE_TILES = 'tiles';
+ const SELECTION_MODE_NORMAL = 'normal';
+ const SELECTION_MODE_FIXED_RATIO = 'fixed-ratio';
+ const SELECTION_MODE_FIXED_SIZE = 'fixed-size';
+
const FILTER_EVERYTHING = 'everything';
protected $brokenImageHash = null;
@@ -203,14 +209,14 @@ class MediaManager extends WidgetBase
$paths = Input::get('paths');
if (!is_array($paths))
- throw new SystemException('Invalid input data');
+ throw new ApplicationException('Invalid input data');
$library = MediaLibrary::instance();
$filesToDelete = [];
foreach ($paths as $pathInfo) {
if (!isset($pathInfo['path']) || !isset($pathInfo['type']))
- throw new SystemException('Invalid input data');
+ throw new ApplicationException('Invalid input data');
if ($pathInfo['type'] == 'file')
$filesToDelete[] = $pathInfo['path'];
@@ -300,7 +306,7 @@ class MediaManager extends WidgetBase
{
$exclude = Input::get('exclude', []);
if (!is_array($exclude))
- throw new SystemException('Invalid input data');
+ throw new ApplicationException('Invalid input data');
$folders = MediaLibrary::instance()->listAllDirectories($exclude);
@@ -336,11 +342,11 @@ class MediaManager extends WidgetBase
$files = Input::get('files', []);
if (!is_array($files))
- throw new SystemException('Invalid input data');
+ throw new ApplicationException('Invalid input data');
$folders = Input::get('folders', []);
if (!is_array($folders))
- throw new SystemException('Invalid input data');
+ throw new ApplicationException('Invalid input data');
$library = MediaLibrary::instance();
@@ -376,9 +382,91 @@ class MediaManager extends WidgetBase
public function onLoadImageCropPopup()
{
+ $path = Input::get('path');
+ $path = MediaLibrary::validatePath($path);
+
+ $selectionParams = $this->getSelectionParams();
+ $this->vars['currentSelectionMode'] = $selectionParams['mode'];
+ $this->vars['currentSelectionWidth'] = $selectionParams['width'];
+ $this->vars['currentSelectionHeight'] = $selectionParams['height'];
+
+ $this->vars['cropSessionKey'] = $cropSessionKey = md5(Form::getSessionKey());
+
+ $urlAndSize = $this->getCropEditImageUrlAndSize($path, $cropSessionKey);
+ $this->vars['imageUrl'] = $urlAndSize['url'];
+ $this->vars['dimensions'] = $urlAndSize['dimensions'];
+
+ $width = $urlAndSize['dimensions'][0];
+ $height = $urlAndSize['dimensions'][1] ? $urlAndSize['dimensions'][1] : 1;
+
+ $this->vars['originalRatio'] = round($width/$height, 5);
+ $this->vars['path'] = $path;
+
return $this->makePartial('image-crop-popup-body');
}
+ public function onEndCroppingSession()
+ {
+ $cropSessionKey = Input::get('cropSessionKey');
+ if (!preg_match('/^[0-9a-z]+$/', $cropSessionKey))
+ throw new ApplicationException('Invalid input data');
+
+ $this->removeCropEditDir($cropSessionKey);
+ }
+
+ public function onCropImage()
+ {
+ $imageSrcPath = trim(Input::get('img'));
+ $selectionData = Input::get('selection');
+ $cropSessionKey = Input::get('cropSessionKey');
+ $path = Input::get('path');
+ $path = MediaLibrary::validatePath($path);
+
+ if (!strlen($imageSrcPath))
+ throw new ApplicationException('Invalid input data');
+
+ if (!preg_match('/^[0-9a-z]+$/', $cropSessionKey))
+ throw new ApplicationException('Invalid input data');
+
+ if (!is_array($selectionData))
+ throw new ApplicationException('Invalid input data');
+
+ $result = $this->cropImage($imageSrcPath, $selectionData, $cropSessionKey, $path);
+
+ $selectionMode = Input::get('selectionMode');
+ $selectionWidth = Input::get('selectionWidth');
+ $selectionHeight = Input::get('selectionHeight');
+
+ $this->setSelectionParams($selectionMode, $selectionWidth, $selectionHeight);
+
+ return $result;
+ }
+
+ public function onResizeImage()
+ {
+ $cropSessionKey = Input::get('cropSessionKey');
+ if (!preg_match('/^[0-9a-z]+$/', $cropSessionKey))
+ throw new ApplicationException('Invalid input data');
+
+ $width = trim(Input::get('width'));
+ if (!strlen($width) || !ctype_digit($width))
+ throw new ApplicationException('Invalid input data');
+
+ $height = trim(Input::get('height'));
+ if (!strlen($height) || !ctype_digit($height))
+ throw new ApplicationException('Invalid input data');
+
+ $path = Input::get('path');
+ $path = MediaLibrary::validatePath($path);
+
+ $params = array(
+ 'width' => $width,
+ 'height' => $height
+ );
+
+ return $this->getCropEditImageUrlAndSize($path, $cropSessionKey, $params);
+ }
+
//
// Methods for th internal use
//
@@ -447,7 +535,7 @@ class MediaManager extends WidgetBase
MediaLibraryItem::FILE_TYPE_AUDIO,
MediaLibraryItem::FILE_TYPE_DOCUMENT,
MediaLibraryItem::FILE_TYPE_VIDEO]))
- throw new SystemException('Invalid input data');
+ throw new ApplicationException('Invalid input data');
return $this->putSession('media_filter', $filter);
}
@@ -473,7 +561,7 @@ class MediaManager extends WidgetBase
MediaLibrary::SORT_BY_TITLE,
MediaLibrary::SORT_BY_SIZE,
MediaLibrary::SORT_BY_MODIFIED]))
- throw new SystemException('Invalid input data');
+ throw new ApplicationException('Invalid input data');
return $this->putSession('media_sort_by', $sortBy);
}
@@ -483,6 +571,51 @@ class MediaManager extends WidgetBase
return $this->getSession('media_sort_by', MediaLibrary::SORT_BY_TITLE);
}
+ protected function getSelectionParams()
+ {
+ $result = $this->getSession('media_crop_selection_params');
+
+ if ($result) {
+ if (!isset($result['mode']))
+ $result['mode'] = MediaManager::SELECTION_MODE_NORMAL;
+
+ if (!isset($result['width']))
+ $result['width'] = null;
+
+ if (!isset($result['height']))
+ $result['height'] = null;
+
+ return $result;
+ }
+
+ return [
+ 'mode'=>MediaManager::SELECTION_MODE_NORMAL,
+ 'width'=>null,
+ 'height'=>null
+ ];
+ }
+
+ protected function setSelectionParams($selectionMode, $selectionWidth, $selectionHeight)
+ {
+ if (!in_array($selectionMode, [
+ MediaManager::SELECTION_MODE_NORMAL,
+ MediaManager::SELECTION_MODE_FIXED_RATIO,
+ MediaManager::SELECTION_MODE_FIXED_SIZE]))
+ throw new ApplicationException('Invalid input data');
+
+ if (strlen($selectionWidth) && !ctype_digit($selectionWidth))
+ throw new ApplicationException('Invalid input data');
+
+ if (strlen($selectionHeight) && !ctype_digit($selectionHeight))
+ throw new ApplicationException('Invalid input data');
+
+ return $this->putSession('media_crop_selection_params', [
+ 'mode'=>$selectionMode,
+ 'width'=>$selectionWidth,
+ 'height'=>$selectionHeight
+ ]);
+ }
+
protected function setSidebarVisible($visible)
{
return $this->putSession('sideba_visible', !!$visible);
@@ -540,7 +673,7 @@ class MediaManager extends WidgetBase
protected function setViewMode($viewMode)
{
if (!in_array($viewMode, [self::VIEW_MODE_GRID, self::VIEW_MODE_LIST, self::VIEW_MODE_TILES]))
- throw new SystemException('Invalid input data');
+ throw new ApplicationException('Invalid input data');
return $this->putSession('view_mode', $viewMode);
}
@@ -807,4 +940,151 @@ class MediaManager extends WidgetBase
return true;
}
+
+ //
+ // Cropping
+ //
+
+ protected function getCropSessionDirPath($cropSessionKey)
+ {
+ return $this->getThumbnailDirectory().'edit-crop-'.$cropSessionKey;
+ }
+
+ protected function getCropEditImageUrlAndSize($path, $cropSessionKey, $params = null)
+ {
+ $sessionDirectoryPath = $this->getCropSessionDirPath($cropSessionKey);
+ $fullSessionDirectoryPath = temp_path($sessionDirectoryPath);
+ $sessionDirectoryCreated = false;
+
+ if (!File::isDirectory($fullSessionDirectoryPath)) {
+ File::makeDirectory($fullSessionDirectoryPath, 0777, true, true);
+ $sessionDirectoryCreated = true;
+ }
+
+ $tempFilePath = null;
+
+ try {
+ $extension = pathinfo($path, PATHINFO_EXTENSION);
+ $library = MediaLibrary::instance();
+ $originalThumbFileName = 'original.'.$extension;
+
+ if (!$params) {
+ // If the target dimensions are not provided, save the original image to the
+ // crop session directory and return its URL.
+
+ $tempFilePath = $fullSessionDirectoryPath.'/'.$originalThumbFileName;
+
+ if (!@File::put($tempFilePath, $library->get($path)))
+ throw new SystemException('Error saving remote file to a temporary location.');
+
+ $url = $this->getThumbnailImageUrl($sessionDirectoryPath.'/'.$originalThumbFileName);
+ $dimensions = getimagesize($tempFilePath);
+
+ return [
+ 'url' => $url,
+ 'dimensions' => $dimensions
+ ];
+ } else {
+ // If the target dimensions are provided, resize the original image and return its URL
+ // and dimensions.
+
+ $originalFilePath = $fullSessionDirectoryPath.'/'.$originalThumbFileName;
+ if (!File::isFile($originalFilePath))
+ throw new SystemException('The original image is not found in the cropping session directory.');
+
+ $resizedThumbFileName = 'resized-'.$params['width'].'-'.$params['height'].'.'.$extension;
+ $tempFilePath = $fullSessionDirectoryPath.'/'.$resizedThumbFileName;
+
+ $resizer = Resizer::open($originalFilePath);
+
+ $resizer->resize($params['width'], $params['height'], 'exact');
+ $resizer->save($tempFilePath, 95);
+
+ $url = $this->getThumbnailImageUrl($sessionDirectoryPath.'/'.$resizedThumbFileName);
+ $dimensions = getimagesize($tempFilePath);
+
+ return [
+ 'url' => $url,
+ 'dimensions' => $dimensions
+ ];
+ }
+ } catch (Exception $ex) {
+ if ($sessionDirectoryCreated)
+ @File::deleteDirectory($fullSessionDirectoryPath);
+
+ if ($tempFilePath)
+ File::delete($tempFilePath);
+
+ throw $ex;
+ }
+ }
+
+ protected function removeCropEditDir($cropSessionKey)
+ {
+ $sessionDirectoryPath = $this->getCropSessionDirPath($cropSessionKey);
+ $fullSessionDirectoryPath = temp_path($sessionDirectoryPath);
+
+ if (File::isDirectory($fullSessionDirectoryPath))
+ @File::deleteDirectory($fullSessionDirectoryPath);
+ }
+
+ protected function cropImage($imageSrcPath, $selectionData, $cropSessionKey, $path)
+ {
+ $originalFileName = basename($path);
+
+ $path = rtrim(dirname($path), '/').'/';
+ $fileName = basename($imageSrcPath);
+
+ if (strpos($fileName, '..') !== false || strpos($fileName, '/') !== false || strpos($fileName, '\\') !== false)
+ throw new SystemException('Invalid image file name.');
+
+ $selectionParams = ['x', 'y', 'w', 'h'];
+
+ foreach ($selectionParams as $paramName) {
+ if (!array_key_exists($paramName, $selectionData))
+ throw new SystemException('Invalid selection data.');
+
+ if (!ctype_digit($selectionData[$paramName]))
+ throw new SystemException('Invalid selection data.');
+ }
+
+ $sessionDirectoryPath = $this->getCropSessionDirPath($cropSessionKey);
+ $fullSessionDirectoryPath = temp_path($sessionDirectoryPath);
+
+ if (!File::isDirectory($fullSessionDirectoryPath))
+ throw new SystemException('The image editing session is not found.');
+
+ // Find the image on the disk and resize it
+ $imagePath = $fullSessionDirectoryPath.'/'.$fileName;
+ if (!File::isFile($imagePath))
+ throw new SystemException('The image is not found on the disk.');
+
+ $extension = pathinfo($originalFileName, PATHINFO_EXTENSION);
+
+ $targetImageName = basename($originalFileName, '.'.$extension).'-'.$selectionData['x']
+ .'-'.$selectionData['y'].'-'.$selectionData['w'].'-'.$selectionData['h'].'-';
+ $targetImageName .= time();
+ $targetImageName .= '.'.$extension;
+
+ $targetTmpPath = $fullSessionDirectoryPath.'/'.$targetImageName;
+
+ if ($selectionData['w'] == 0 || $selectionData['h'] == 0) {
+ // If cropping is not required, copy the oiginal image to the target destination.
+ File::copy($imagePath, $targetTmpPath);
+ }
+ else {
+ $resizer = Resizer::open($imagePath);
+
+ $resizer->resample($selectionData['x'], $selectionData['y'], $selectionData['w'], $selectionData['h'], $selectionData['w'], $selectionData['h']);
+ $resizer->save($targetTmpPath, 95);
+ }
+
+ // Upload the cropped file to the Library
+ $targetPath = $path.'cropped-images/'.$targetImageName;
+
+ $library = MediaLibrary::instance();
+
+ $library->put($targetPath, file_get_contents($targetTmpPath));
+ return $library->getPathUrl($targetPath);
+ }
}
\ No newline at end of file
diff --git a/modules/cms/widgets/mediamanager/assets/css/mediamanager.css b/modules/cms/widgets/mediamanager/assets/css/mediamanager.css
index b9e98f94f..6c8430b9e 100644
--- a/modules/cms/widgets/mediamanager/assets/css/mediamanager.css
+++ b/modules/cms/widgets/mediamanager/assets/css/mediamanager.css
@@ -25,7 +25,8 @@ div[data-control="media-manager"] .empty-library {
}
div[data-control="media-manager"] p.thumbnail-error-message {
font-size: 12px;
- margin-top: 25px;
+ margin: 10px;
+ line-height: 160%;
color: #bdc3c7;
}
div[data-control="media-manager"] .media-list {
@@ -279,8 +280,10 @@ div[data-control="media-manager"] .sidebar-image-placeholder.no-border {
}
div[data-control="media-manager"] .sidebar-image-placeholder p {
font-size: 12px;
- margin-top: 25px;
+ margin: 10px;
+ line-height: 160%;
color: #bdc3c7;
+ margin-top: 25px;
}
div[data-control="media-manager"] .list-container {
position: relative;
@@ -394,6 +397,97 @@ div[data-control="media-manager"] button[data-command="toggle-sidebar"].sidebar-
-ms-transform: rotate(180deg) translate(0, 0);
transform: rotate(180deg) translate(0, 0);
}
+[data-control="media-manager-crop-tool"] .image_area {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ overflow: auto;
+}
+[data-control="media-manager-crop-tool"] .image_area .jcrop-holder {
+ background-color: transparent!important;
+}
+[data-control="media-manager-crop-tool"] img {
+ cursor: crosshair;
+ display: block;
+}
+[data-control="media-manager-crop-tool"].has-rulers .ruler-container .layout-relative {
+ overflow: hidden;
+}
+[data-control="media-manager-crop-tool"].has-rulers .ruler-container.horizontal .layout-cell {
+ height: 20px;
+}
+[data-control="media-manager-crop-tool"].has-rulers .ruler-container.horizontal .layout-relative {
+ width: 100%;
+}
+[data-control="media-manager-crop-tool"].has-rulers .ruler-container.vertical {
+ width: 20px;
+}
+[data-control="media-manager-crop-tool"].has-rulers .ruler-container.vertical .layout-relative {
+ height: 100%;
+}
+[data-control="media-manager-crop-tool"].has-rulers .ruler {
+ position: absolute;
+ height: 20px;
+ margin-left: -3px;
+ background: #555;
+}
+[data-control="media-manager-crop-tool"].has-rulers .ruler ul {
+ margin: 0;
+ padding: 0;
+ white-space: nowrap;
+ font-size: 0;
+}
+[data-control="media-manager-crop-tool"].has-rulers .ruler li {
+ margin: 0;
+ padding: 0 0 0 40px;
+ list-style: none;
+ display: inline-block;
+ width: 24px;
+ margin: 0px -10px 0px -14px;
+ -webkit-box-sizing: content-box;
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+ text-align: left;
+ position: relative;
+ font-size: 10px;
+ line-height: 20px;
+ color: #ecf0f1;
+ font-family: Arial, sans-serif;
+}
+[data-control="media-manager-crop-tool"].has-rulers .ruler li:before,
+[data-control="media-manager-crop-tool"].has-rulers .ruler li:after {
+ content: ' ';
+ position: absolute;
+ border-left: 1px solid #8e8e8e;
+}
+[data-control="media-manager-crop-tool"].has-rulers .ruler li:before {
+ height: 20px;
+ top: 0;
+ left: -3px;
+}
+[data-control="media-manager-crop-tool"].has-rulers .ruler li:after {
+ height: 3px;
+ bottom: 0;
+ left: 20px;
+}
+[data-control="media-manager-crop-tool"].has-rulers .ruler li:first-child:after {
+ display: none;
+}
+[data-control="media-manager-crop-tool"].has-rulers .ruler[data-control=v-ruler] {
+ -webkit-transform: rotateZ(90deg);
+ -ms-transform: rotateZ(90deg);
+ transform: rotateZ(90deg);
+ -webkit-transform-origin: left top;
+ -moz-transform-origin: left top;
+ -ms-transform-origin: left top;
+ transform-origin: left top;
+ left: 23px;
+ top: -23px;
+}
+[data-control="media-manager-crop-tool"].has-rulers .ruler[data-control=v-ruler] li:after {
+ top: 0;
+ left: auto;
+}
body:not(.no-select) div[data-control="media-manager"] .media-list.tiles li:hover .icon-container {
background: #4da7e8 !important;
border-color: #2581b8;
@@ -408,17 +502,17 @@ body:not(.no-select) div[data-control="media-manager"] .media-list.tiles li:hove
body:not(.no-select) div[data-control="media-manager"] .media-list.tiles li:hover h4 {
padding-right: 20px!important;
}
-body:not(.no-select) div[data-control="media-manager"] .media-list.list li:hover:not(:active) {
+body:not(.no-select) div[data-control="media-manager"] .media-list.list li:hover {
background: #4da7e8 !important;
}
-body:not(.no-select) div[data-control="media-manager"] .media-list.list li:hover:not(:active) i,
-body:not(.no-select) div[data-control="media-manager"] .media-list.list li:hover:not(:active) p.size {
+body:not(.no-select) div[data-control="media-manager"] .media-list.list li:hover i,
+body:not(.no-select) div[data-control="media-manager"] .media-list.list li:hover p.size {
color: #ecf0f1;
}
-body:not(.no-select) div[data-control="media-manager"] .media-list.list li:hover:not(:active) h4 {
+body:not(.no-select) div[data-control="media-manager"] .media-list.list li:hover h4 {
color: white;
}
-body:not(.no-select) div[data-control="media-manager"] .media-list.list li:hover:not(:active) .icon-container {
+body:not(.no-select) div[data-control="media-manager"] .media-list.list li:hover .icon-container {
border-right-color: #4da7e8 !important;
}
body:not(.no-select) div[data-control="media-manager"] .media-list.list li:hover h4 {
diff --git a/modules/cms/widgets/mediamanager/assets/js/mediamanager.imagecroppopup.js b/modules/cms/widgets/mediamanager/assets/js/mediamanager.imagecroppopup.js
index 479d76f80..5f54ecf12 100644
--- a/modules/cms/widgets/mediamanager/assets/js/mediamanager.imagecroppopup.js
+++ b/modules/cms/widgets/mediamanager/assets/js/mediamanager.imagecroppopup.js
@@ -11,6 +11,17 @@
var MediaManagerImageCropPopup = function(path, options) {
this.$popupRootElement = null
+ this.$popupElement = null
+ this.selectionSizeLabel = null
+ this.imageArea = null
+ this.hRulerHolder = null
+ this.vRulerHolder = null
+
+ this.rulersVisible = false
+ this.prevScrollTop = 0
+ this.prevScrollLeft = 0
+
+ this.jCrop = null
this.options = $.extend({}, MediaManagerImageCropPopup.DEFAULTS, options)
this.path = path
@@ -26,10 +37,15 @@
MediaManagerImageCropPopup.prototype.dispose = function() {
this.unregisterHandlers()
+ this.removeAttachedControls()
this.$popupRootElement.remove()
this.$popupRootElement = null
this.$popupElement = null
+ this.selectionSizeLabel = null
+ this.imageArea = null
+ this.hRulerHolder = null
+ this.vRulerHolder = null
BaseProto.dispose.call(this)
}
@@ -51,7 +67,8 @@
extraData: data,
size: 'adaptive',
adaptiveHeight: true,
- handler: this.options.alias + '::onLoadImageCropPopup'
+ handler: this.options.alias + '::onLoadImageCropPopup',
+ zIndex: 1200 // Media Manager can be opened in a popup, so this new popup should have a higher z-index
})
}
@@ -61,6 +78,31 @@
}
MediaManagerImageCropPopup.prototype.unregisterHandlers = function() {
+ this.$popupElement.off('change', '[data-control="selection-mode"]', this.proxy(this.onSelectionModeChanged))
+ this.$popupElement.off('click', '[data-command]', this.proxy(this.onCommandClick))
+ this.$popupElement.off('shown.oc.popup', 'button[data-command=resize]', this.proxy(this.onResizePopupShown))
+ this.$popupElement.off('hidden.oc.popup', 'button[data-command=resize]', this.proxy(this.onResizePopupHidden))
+
+ if (this.rulersVisible) {
+ var $cropToolRoot = this.$popupElement.find('[data-control=media-manager-crop-tool]')
+ this.imageArea.removeEventListener('scroll', this.proxy(this.onImageScroll))
+ }
+
+ this.getWidthInput().off('change', this.proxy(this.onSizeInputChange))
+ this.getHeightInput().off('change', this.proxy(this.onSizeInputChange))
+ }
+
+ MediaManagerImageCropPopup.prototype.removeAttachedControls = function() {
+ if (this.$popupElement) {
+ // Note - the controls are destroyed and removed from DOM. If they're just destroyed,
+ // the JS plugins could be re-attached to them on window.onresize. -ab
+ this.$popupElement.find('[data-control="selection-mode"]').select2('destroy').remove()
+ this.$popupElement.find('[data-control=toolbar]').toolbar('dispose').remove()
+
+ this.jCrop.destroy()
+ }
+
+ this.jCrop = null
}
MediaManagerImageCropPopup.prototype.hide = function() {
@@ -68,10 +110,272 @@
this.$popupElement.trigger('close.oc.popup')
}
+ MediaManagerImageCropPopup.prototype.getSelectionMode = function() {
+ return this.$popupElement.find('[data-control="selection-mode"]').val()
+ }
+
+ MediaManagerImageCropPopup.prototype.initRulers = function() {
+ if (!Modernizr.csstransforms)
+ return
+
+ var $cropToolRoot = this.$popupElement.find('[data-control=media-manager-crop-tool]'),
+ width = $cropToolRoot.data('image-width'),
+ height = $cropToolRoot.data('image-height')
+
+ if (!width || !height)
+ return
+
+ if ($cropToolRoot.width() > width)
+ width = $(window).width()
+
+ if ($cropToolRoot.height() > height)
+ height = $(window).height()
+
+ $cropToolRoot.find('.ruler-container').removeClass('hide')
+
+ $cropToolRoot.addClass('has-rulers')
+
+ var $hRuler = $cropToolRoot.find('[data-control=h-ruler]'),
+ $vRuler = $cropToolRoot.find('[data-control=v-ruler]'),
+ hTicks = width / 40 + 1,
+ vTicks = height / 40 + 1
+
+ this.createRulerTicks($hRuler, hTicks)
+ this.createRulerTicks($vRuler, vTicks)
+
+ this.rulersVisible = true
+
+ this.imageArea.addEventListener('scroll', this.proxy(this.onImageScroll))
+
+ this.hRulerHolder = $cropToolRoot.find('.ruler-container.horizontal .layout-relative').get(0)
+ this.vRulerHolder = $cropToolRoot.find('.ruler-container.vertical .layout-relative').get(0)
+ }
+
+ MediaManagerImageCropPopup.prototype.createRulerTicks = function($rulerElement, count) {
+ var list = document.createElement('ul')
+
+ for (var i=0; i <= count; i++) {
+ var li = document.createElement('li')
+ li.textContent = i*40
+
+ list.appendChild(li)
+ }
+
+ $rulerElement.append(list)
+ }
+
+ MediaManagerImageCropPopup.prototype.initJCrop = function() {
+ this.jCrop = $.Jcrop($(this.imageArea).find('img').get(0), {
+ shade: true,
+ onChange: this.proxy(this.onSelectionChanged)
+ })
+ }
+
+ MediaManagerImageCropPopup.prototype.fixDimensionValue = function(value) {
+ var result = value.replace(/[^0-9]+/, '')
+
+ if (!result.length)
+ result = 200
+
+ if (result == '0')
+ result = 1
+
+ return result
+ }
+
+ MediaManagerImageCropPopup.prototype.getWidthInput = function() {
+ return this.$popupElement.find('[data-control="crop-width-input"]')
+ }
+
+ MediaManagerImageCropPopup.prototype.getHeightInput = function() {
+ return this.$popupElement.find('[data-control="crop-height-input"]')
+ }
+
+ MediaManagerImageCropPopup.prototype.applySelectionMode = function() {
+ if (!this.jCrop)
+ return
+
+ var $widthInput = this.getWidthInput(),
+ $heightInput = this.getHeightInput(),
+ width = this.fixDimensionValue($widthInput.val()),
+ height = this.fixDimensionValue($heightInput.val()),
+ mode = this.getSelectionMode()
+
+ switch (mode) {
+ case 'fixed-ratio' :
+ this.jCrop.setOptions({
+ aspectRatio: width/height,
+ minSize: [0, 0],
+ maxSize: [0, 0],
+ allowResize: true
+ })
+ break
+ case 'fixed-size' :
+ this.jCrop.setOptions({
+ aspectRatio: 0,
+ minSize: [width, height],
+ maxSize: [width, height],
+ allowResize: false
+ })
+ break
+ case 'normal' :
+ this.jCrop.setOptions({
+ aspectRatio: 0,
+ minSize: [0, 0],
+ maxSize: [0, 0],
+ allowResize: true
+ })
+ break
+ }
+ }
+
+ MediaManagerImageCropPopup.prototype.cropAndInsert = function() {
+ var data = {
+ img: $(this.imageArea).find('img').attr('src'),
+ selection: this.jCrop.tellSelect()
+ }
+
+ $.oc.stripeLoadIndicator.show()
+ this.$popupElement.find('form').request(
+ this.options.alias+'::onCropImage', {
+ data: data
+ }
+ ).always(function() {
+ $.oc.stripeLoadIndicator.hide()
+ }).done(this.proxy(this.onImageCropped))
+ }
+
+ MediaManagerImageCropPopup.prototype.onImageCropped = function(response) {
+ this.hide()
+
+ if (this.options.onDone !== undefined)
+ this.options.onDone(response.result)
+ }
+
+ MediaManagerImageCropPopup.prototype.showResizePopup = function() {
+ this.$popupElement.find('button[data-command=resize]').popup({
+ content: this.$popupElement.find('[data-control="resize-template"]').html(),
+ zIndex: 1220
+ })
+ }
+
+ MediaManagerImageCropPopup.prototype.onResizePopupShown = function(ev, button, popup) {
+ var $popup = $(popup),
+ $widthControl = $popup.find('input[name=width]'),
+ $heightControl = $popup.find('input[name=height]'),
+ imageWidth = this.fixDimensionValue(this.$popupElement.find('input[data-control=dimension-width]').val()),
+ imageHeight = this.fixDimensionValue(this.$popupElement.find('input[data-control=dimension-height]').val())
+
+ $widthControl.val(imageWidth)
+ $heightControl.val(imageHeight)
+
+ $widthControl.focus()
+
+ $popup.on('submit.media', 'form', this.proxy(this.onResizeSubmit))
+ $widthControl.on('keyup.media', this.proxy(this.onResizeDimensionKeyUp))
+ $heightControl.on('keyup.media', this.proxy(this.onResizeDimensionKeyUp))
+
+ $widthControl.on('change.media', this.proxy(this.onResizeDimensionChanged))
+ $heightControl.on('change.media', this.proxy(this.onResizeDimensionChanged))
+ }
+
+ MediaManagerImageCropPopup.prototype.onResizePopupHidden = function(ev, button, popup) {
+ var $popup = $(popup),
+ $widthControl = $popup.find('input[name=width]'),
+ $heightControl = $popup.find('input[name=height]')
+
+ $popup.off('.media', 'form')
+ $widthControl.off('.media')
+ $heightControl.off('.media')
+ }
+
+ MediaManagerImageCropPopup.prototype.onResizeDimensionKeyUp = function(ev) {
+ var $target = $(ev.target),
+ targetValue = this.fixDimensionValue($target.val()),
+ otherDimensionName = $target.attr('name') == 'width' ? 'height' : 'width',
+ $otherInput = $target.closest('form').find('input[name='+otherDimensionName+']'),
+ ratio = this.$popupElement.find('[data-control=original-ratio]').val(),
+ value = otherDimensionName == 'height' ? targetValue / ratio : targetValue * ratio
+
+ $otherInput.val(Math.round(value))
+ }
+
+ MediaManagerImageCropPopup.prototype.onResizeDimensionChanged = function(ev) {
+ var $target = $(ev.target)
+
+ $target.val(this.fixDimensionValue($target.val()))
+ }
+
+ MediaManagerImageCropPopup.prototype.onResizeSubmit = function(ev) {
+ var data = {
+ cropSessionKey: this.$popupElement.find('input[name=cropSessionKey]').val(),
+ path: this.$popupElement.find('input[name=path]').val()
+ }
+
+ $.oc.stripeLoadIndicator.show()
+ $(ev.target).request(this.options.alias+'::onResizeImage', {
+ data: data
+ }).always(function() {
+ $.oc.stripeLoadIndicator.hide()
+ }).done(this.proxy(this.imageResized))
+
+ ev.preventDefault()
+ return false
+ }
+
+ MediaManagerImageCropPopup.prototype.imageResized = function(response) {
+ this.$popupElement.find('button[data-command=resize]').popup('hide')
+
+ this.updateImage(response.url, response.dimensions[0], response.dimensions[1])
+ }
+
+ MediaManagerImageCropPopup.prototype.updateImage = function(url, width, hegiht) {
+ this.jCrop.destroy()
+
+ this.$popupElement.find('span[data-label=width]').text(width)
+ this.$popupElement.find('span[data-label=height]').text(hegiht)
+
+ this.$popupElement.find('input[data-control=dimension-width]').val(width)
+ this.$popupElement.find('input[data-control=dimension-height]').val(hegiht)
+
+ var $imageArea = $(this.imageArea)
+ $imageArea.find('img').remove()
+
+ var $img = $('
').attr('src', url)
+ $img.one('load', this.proxy(this.initJCrop))
+
+ $imageArea.append($img)
+
+ this.imageArea.scrollTop = 0
+ this.imageArea.scrollLeft = 0
+ this.onImageScroll()
+ }
+
+ MediaManagerImageCropPopup.prototype.undoResizing = function() {
+ this.updateImage(
+ this.$popupElement.find('input[data-control=original-url]').val(),
+ this.$popupElement.find('input[data-control=original-width]').val(),
+ this.$popupElement.find('input[data-control=original-height]').val()
+ )
+ }
+
+ MediaManagerImageCropPopup.prototype.updateSelectionSizeLabel = function(width, height) {
+ if (width == 0 && height == 0) {
+ this.selectionSizeLabel.setAttribute('class', 'hide')
+ return
+ }
+
+ this.selectionSizeLabel.setAttribute('class', '')
+ this.selectionSizeLabel.querySelector('[data-label=selection-width]').textContent = width
+ this.selectionSizeLabel.querySelector('[data-label=selection-height]').textContent = height
+ }
+
// EVENT HANDLERS
// ============================
MediaManagerImageCropPopup.prototype.onPopupHidden = function(event, element, popup) {
+ this.$popupElement.find('form').request(this.options.alias+'::onEndCroppingSession')
+
// Release clickedElement reference inside redactor.js
// If we don't do it, the image editor popup DOM elements
// won't be removed from the memory.
@@ -82,6 +386,83 @@
MediaManagerImageCropPopup.prototype.onPopupShown = function(event, element, popup) {
this.$popupElement = popup
+ this.$popupElement.on('change', '[data-control="selection-mode"]', this.proxy(this.onSelectionModeChanged))
+ this.$popupElement.on('click', '[data-command]', this.proxy(this.onCommandClick))
+ this.$popupElement.on('shown.oc.popup', 'button[data-command=resize]', this.proxy(this.onResizePopupShown))
+ this.$popupElement.on('hidden.oc.popup', 'button[data-command=resize]', this.proxy(this.onResizePopupHidden))
+
+ this.imageArea = popup.find('[data-control=media-manager-crop-tool]').get(0).querySelector('.image_area')
+ this.selectionSizeLabel = popup.find('[data-label="selection-size"]').get(0)
+
+ this.getWidthInput().on('change', this.proxy(this.onSizeInputChange))
+ this.getHeightInput().on('change', this.proxy(this.onSizeInputChange))
+
+ this.initRulers()
+ this.initJCrop()
+ }
+
+ MediaManagerImageCropPopup.prototype.onSelectionModeChanged = function() {
+ var mode = this.getSelectionMode(),
+ $widthInput = this.getWidthInput(),
+ $heightInput = this.getHeightInput()
+
+ if (mode === 'normal') {
+ $widthInput.attr('disabled', 'disabled')
+ $heightInput.attr('disabled', 'disabled')
+ } else {
+ $widthInput.removeAttr('disabled')
+ $heightInput.removeAttr('disabled')
+
+ $widthInput.val(this.fixDimensionValue($widthInput.val()))
+ $heightInput.val(this.fixDimensionValue($heightInput.val()))
+ }
+
+ this.applySelectionMode()
+ }
+
+ MediaManagerImageCropPopup.prototype.onImageScroll = function() {
+ var scrollTop = this.imageArea.scrollTop,
+ scrollLeft = this.imageArea.scrollLeft
+
+ if (this.prevScrollTop != scrollTop) {
+ this.prevScrollTop = scrollTop
+
+ this.vRulerHolder.scrollTop = scrollTop
+ }
+
+ if (this.prevScrollLeft != scrollLeft) {
+ this.prevScrollLeft = scrollLeft
+
+ this.hRulerHolder.scrollLeft = scrollLeft
+ }
+ }
+
+ MediaManagerImageCropPopup.prototype.onSizeInputChange = function(ev) {
+ var $target = $(ev.target)
+
+ $target.val(this.fixDimensionValue($target.val()))
+
+ this.applySelectionMode()
+ }
+
+ MediaManagerImageCropPopup.prototype.onCommandClick = function(ev) {
+ var command = $(ev.currentTarget).data('command')
+
+ switch (command) {
+ case 'insert' :
+ this.cropAndInsert()
+ break
+ case 'resize' :
+ this.showResizePopup()
+ break
+ case 'undo-resizing' :
+ this.undoResizing()
+ break
+ }
+ }
+
+ MediaManagerImageCropPopup.prototype.onSelectionChanged = function(c) {
+ this.updateSelectionSizeLabel(c.w, c.h)
}
MediaManagerImageCropPopup.DEFAULTS = {
diff --git a/modules/cms/widgets/mediamanager/assets/js/mediamanager.js b/modules/cms/widgets/mediamanager/assets/js/mediamanager.js
index b260d57f7..fd3dba667 100644
--- a/modules/cms/widgets/mediamanager/assets/js/mediamanager.js
+++ b/modules/cms/widgets/mediamanager/assets/js/mediamanager.js
@@ -720,14 +720,21 @@
return
}
- new $.oc.mediaManager.imageCropPopup(
- selectedItems[0].getAttribute('data-path'), {
- alias: this.options.alias
+ var path = selectedItems[0].getAttribute('data-path')
+
+ new $.oc.mediaManager.imageCropPopup(path, {
+ alias: this.options.alias,
+ onDone: callback
})
}
- MediaManager.prototype.onImageCropped = function(imageItem) {
+ MediaManager.prototype.onImageCropped = function(imageUrl) {
+ var item = {
+ documentType: 'image',
+ publicUrl: imageUrl
+ }
+ this.$el.trigger('popupcommand', ['insert-cropped', item])
}
//
@@ -819,7 +826,8 @@
MediaManager.prototype.createFolder = function(ev) {
$(ev.target).popup({
- content: this.$el.find('[data-control="new-folder-template"]').html()
+ content: this.$el.find('[data-control="new-folder-template"]').html(),
+ zIndex: 1200 // Media Manager can be opened in a popup, so this new popup should have a higher z-index
})
}
@@ -882,7 +890,8 @@
$(ev.target).popup({
handler: this.options.alias+'::onLoadMovePopup',
- extraData: data
+ extraData: data,
+ zIndex: 1200 // Media Manager can be opened in a popup, so this new popup should have a higher z-index
})
}
diff --git a/modules/cms/widgets/mediamanager/assets/js/mediamanager.popup.js b/modules/cms/widgets/mediamanager/assets/js/mediamanager.popup.js
index 8182d8f42..08178b12c 100644
--- a/modules/cms/widgets/mediamanager/assets/js/mediamanager.popup.js
+++ b/modules/cms/widgets/mediamanager/assets/js/mediamanager.popup.js
@@ -56,7 +56,7 @@
bottomToolbar: this.options.bottomToolbar ? 1 : 0,
cropAndInsertButton: this.options.cropAndInsertButton ? 1 : 0
}
-console.log(data)
+
this.$popupRootElement.popup({
extraData: data,
size: 'adaptive',
@@ -81,6 +81,11 @@ console.log(data)
this.options.onInsert.call(this, items)
}
+ MediaManagerPopup.prototype.insertCroppedImage = function(imageItem) {
+ if (this.options.onInsert !== undefined)
+ this.options.onInsert.call(this, [imageItem])
+ }
+
// EVENT HANDLERS
// ============================
@@ -110,11 +115,14 @@ console.log(data)
this.getMediaManagerElement().find('[data-control="sorting"]').focus().blur()
}
- MediaManagerPopup.prototype.onPopupCommand = function(ev, command) {
+ MediaManagerPopup.prototype.onPopupCommand = function(ev, command, param) {
switch (command) {
case 'insert' :
this.insertMedia()
break;
+ case 'insert-cropped' :
+ this.insertCroppedImage(param)
+ break;
}
return false
diff --git a/modules/cms/widgets/mediamanager/assets/less/mediamanager.less b/modules/cms/widgets/mediamanager/assets/less/mediamanager.less
index 508ba4c21..e1ddfdc8c 100644
--- a/modules/cms/widgets/mediamanager/assets/less/mediamanager.less
+++ b/modules/cms/widgets/mediamanager/assets/less/mediamanager.less
@@ -75,7 +75,8 @@ div[data-control="media-manager"] {
.icon-message() {
font-size: 12px;
- margin-top: 25px;
+ margin: 10px;
+ line-height: 160%;
color: #bdc3c7;
}
@@ -327,6 +328,7 @@ div[data-control="media-manager"] {
p {
.icon-message();
+ margin-top: 25px;
}
}
@@ -466,6 +468,115 @@ div[data-control="media-manager"] {
}
}
+[data-control="media-manager-crop-tool"] {
+ .image_area {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+
+ overflow: auto;
+
+ .jcrop-holder {
+ background-color: transparent!important;
+ }
+ }
+
+ img {
+ cursor: crosshair;
+ display: block;
+ }
+
+ &.has-rulers {
+ .ruler-container {
+ .layout-relative {
+ overflow: hidden;
+ }
+
+ &.horizontal {
+ .layout-cell {
+ height: 20px;
+ }
+
+ .layout-relative {
+ width: 100%;
+ }
+ }
+
+ &.vertical {
+ width: 20px;
+ .layout-relative {
+ height: 100%;
+ }
+ }
+ }
+
+ .ruler {
+ position: absolute;
+ height: 20px;
+ margin-left: -3px;
+ background: #555;
+
+ ul {
+ margin: 0;
+ padding: 0;
+ white-space: nowrap;
+ font-size: 0;
+ }
+
+ li {
+ margin: 0;
+ padding: 0 0 0 40px;
+ list-style: none;
+ display: inline-block;
+ width: 24px;
+ margin: 0px -10px 0px -14px;
+ .box-sizing(content-box);
+ text-align: left;
+ position: relative;
+ font-size: 10px;
+ line-height: 20px;
+ color: #ecf0f1;
+ font-family: Arial, sans-serif;
+
+ &:before, &:after {
+ content: ' ';
+ position: absolute;
+ border-left: 1px solid #8e8e8e;
+ }
+
+ &:before {
+ height: 20px;
+ top: 0;
+ left: -3px;
+ }
+
+ &:after {
+ height: 3px;
+ bottom: 0;
+ left: 20px;
+ }
+
+ &:first-child:after {
+ display: none;
+ }
+ }
+ }
+
+ .ruler[data-control=v-ruler] {
+ .transform(~'rotateZ(90deg)');
+ .transform-origin(~'left top');
+ left: 23px;
+ top: -23px;
+
+ & li:after {
+ top: 0;
+ left: auto;
+ }
+ }
+
+ }
+}
+
body:not(.no-select) {
div[data-control="media-manager"] .media-list {
&.tiles {
@@ -479,7 +590,7 @@ body:not(.no-select) {
}
&.list {
- li:hover:not(:active) {
+ li:hover {
.media-selected-list();
}
diff --git a/modules/cms/widgets/mediamanager/partials/_crop-tool-image-area.htm b/modules/cms/widgets/mediamanager/partials/_crop-tool-image-area.htm
new file mode 100644
index 000000000..97cb2d4f8
--- /dev/null
+++ b/modules/cms/widgets/mediamanager/partials/_crop-tool-image-area.htm
@@ -0,0 +1,33 @@
+
+ data-image-width="= $dimensions[0] ?>"
+ data-image-height="= $dimensions[1] ?>"
+
+>
+
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/modules/cms/widgets/mediamanager/partials/_crop-toolbar.htm b/modules/cms/widgets/mediamanager/partials/_crop-toolbar.htm
new file mode 100644
index 000000000..1203cc141
--- /dev/null
+++ b/modules/cms/widgets/mediamanager/partials/_crop-toolbar.htm
@@ -0,0 +1,41 @@
+ trans('cms::lang.media.selection_mode_normal'),
+ Cms\Widgets\MediaManager::SELECTION_MODE_FIXED_RATIO => trans('cms::lang.media.selection_mode_fixed_ratio'),
+ Cms\Widgets\MediaManager::SELECTION_MODE_FIXED_SIZE => trans('cms::lang.media.selection_mode_fixed_size')
+ ];
+
+ $sizeDisabledAttr = $currentSelectionMode == Cms\Widgets\MediaManager::SELECTION_MODE_NORMAL ? 'disabled="disabled"' : null;
+?>
+
+
\ No newline at end of file
diff --git a/modules/cms/widgets/mediamanager/partials/_generic-list.htm b/modules/cms/widgets/mediamanager/partials/_generic-list.htm
index ae1185bbe..8d5cbc5f6 100644
--- a/modules/cms/widgets/mediamanager/partials/_generic-list.htm
+++ b/modules/cms/widgets/mediamanager/partials/_generic-list.htm
@@ -39,6 +39,7 @@
href="#"
data-rename
data-control="popup"
+ data-z-index="1200"
data-request-data="path: '= e($item->path) ?>', listId: '= $listElementId ?>', type: '= $item->type ?>'"
data-handler="= $this->getEventHandler('onLoadRenamePopup') ?>"
>
diff --git a/modules/cms/widgets/mediamanager/partials/_image-crop-popup-body.htm b/modules/cms/widgets/mediamanager/partials/_image-crop-popup-body.htm
index 7e376e31d..17a736a90 100644
--- a/modules/cms/widgets/mediamanager/partials/_image-crop-popup-body.htm
+++ b/modules/cms/widgets/mediamanager/partials/_image-crop-popup-body.htm
@@ -1,3 +1,43 @@
= Form::open(['class'=>'layout', 'onsubmit'=>'return false']) ?>
- Something
+
+ = $this->makePartial('crop-toolbar') ?>
+
+
+ = $this->makePartial('crop-tool-image-area') ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ = $this->makePartial('resize-image-form') ?>
= Form::close() ?>
\ No newline at end of file
diff --git a/modules/cms/widgets/mediamanager/partials/_list-grid.htm b/modules/cms/widgets/mediamanager/partials/_list-grid.htm
index 43cf3a0d3..d4eb9f3f6 100644
--- a/modules/cms/widgets/mediamanager/partials/_list-grid.htm
+++ b/modules/cms/widgets/mediamanager/partials/_list-grid.htm
@@ -41,6 +41,7 @@
data-control="popup"
data-request-data="path: '= e($item->path) ?>', listId: '= $listElementId ?>', type: '= $item->type ?>'"
data-handler="= $this->getEventHandler('onLoadRenamePopup') ?>"
+ data-z-index="1200"
>
diff --git a/modules/cms/widgets/mediamanager/partials/_resize-image-form.htm b/modules/cms/widgets/mediamanager/partials/_resize-image-form.htm
new file mode 100644
index 000000000..c076177ba
--- /dev/null
+++ b/modules/cms/widgets/mediamanager/partials/_resize-image-form.htm
@@ -0,0 +1,31 @@
+