diff --git a/src/Umbraco.Core/Constants-PropertyEditors.cs b/src/Umbraco.Core/Constants-PropertyEditors.cs index c0e2c49b38..08a9fd7849 100644 --- a/src/Umbraco.Core/Constants-PropertyEditors.cs +++ b/src/Umbraco.Core/Constants-PropertyEditors.cs @@ -139,7 +139,7 @@ namespace Umbraco.Core ///// ///// Alias for the Image Cropper datatype. ///// - //public const string ImageCropperAlias = "Umbraco.ImageCropper"; + public const string ImageCropperAlias = "Umbraco.ImageCropper"; /// /// Guid for the Integer datatype. diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/mocks/big-image.jpg b/src/Umbraco.Web.UI.Client/src/assets/img/mocks/big-image.jpg new file mode 100644 index 0000000000..fd62385a83 Binary files /dev/null and b/src/Umbraco.Web.UI.Client/src/assets/img/mocks/big-image.jpg differ diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/mocks/big-thumb.jpg b/src/Umbraco.Web.UI.Client/src/assets/img/mocks/big-thumb.jpg new file mode 100644 index 0000000000..ab33fb4a1c Binary files /dev/null and b/src/Umbraco.Web.UI.Client/src/assets/img/mocks/big-thumb.jpg differ diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/imaging/umbimagecrop.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/imaging/umbimagecrop.directive.js index fbe6a0456c..1a31a2e53f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/imaging/umbimagecrop.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/imaging/umbimagecrop.directive.js @@ -5,21 +5,21 @@ * @function **/ angular.module("umbraco.directives") - .directive('umbImageCrop', function ($timeout, localizationService, $log) { + .directive('umbImageCrop', + function ($timeout, localizationService, cropperHelper, $log) { return { restrict: 'E', replace: true, templateUrl: 'views/directives/imaging/umb-image-crop.html', scope: { src: '=', - width: '=', - height: '=', - crop: "=" + width: '@', + height: '@', + crop: "=", + center: "=", }, + link: function(scope, element, attrs) { - - scope.scale = 100; - //if image is over this, we re-calculate the editors global ratio //this will not have an effect on the result, since that is returned in percentage scope.maxHeight = 500; @@ -32,17 +32,23 @@ angular.module("umbraco.directives") image: {}, cropper:{}, viewport:{}, - margin: 20, - ratio: 1 + margin: 40, + scale: { + min: 0.3, + max: 3, + current: 1 + } }; - scope.style = function () { - return { - 'height': (parseInt(scope.height, 10) + 2 * scope.dimensions.margin) + 'px', - 'width': (parseInt(scope.width, 10) + 2 * scope.dimensions.margin) + 'px' - }; - }; + //live rendering of viewport and image styles + scope.style = function () { + return { + 'height': (parseInt(scope.height, 10) + 2 * scope.dimensions.margin) + 'px', + 'width': (parseInt(scope.width, 10) + 2 * scope.dimensions.margin) + 'px' + }; + }; + //elements var $viewport = element.find(".viewport"); @@ -51,11 +57,31 @@ angular.module("umbraco.directives") var $container = element.find(".crop-container"); //default constraints for drag n drop - var constraints = {left: {max: 20, min: 20}, top: {max: 20, min: 20}, }; - - var setDimensions = function(){ - scope.dimensions.image.width = $image.width(); - scope.dimensions.image.height = $image.height(); + var constraints = {left: {max: scope.dimensions.margin, min: scope.dimensions.margin}, top: {max: scope.dimensions.margin, min: scope.dimensions.margin}, }; + scope.constraints = constraints; + + + //set constaints for cropping drag and drop + var setConstraints = function(){ + constraints.left.min = scope.dimensions.margin + scope.dimensions.cropper.width - scope.dimensions.image.width; + constraints.top.min = scope.dimensions.margin + scope.dimensions.cropper.height - scope.dimensions.image.height; + }; + + + var setDimensions = function(originalImage){ + originalImage.width("auto"); + originalImage.height("auto"); + + var image = {}; + image.originalWidth = originalImage.width(); + image.originalHeight = originalImage.height(); + + image.width = image.originalWidth; + image.height = image.originalHeight; + image.left = originalImage[0].offsetLeft; + image.top = originalImage[0].offsetTop; + + scope.dimensions.image = image; scope.dimensions.viewport.width = $viewport.width(); scope.dimensions.viewport.height = $viewport.height(); @@ -64,39 +90,12 @@ angular.module("umbraco.directives") scope.dimensions.cropper.height = scope.dimensions.viewport.height - 2 * scope.dimensions.margin; }; - var setImageSize = function(width, height){ - $image.width(width); - $image.height(height); - scope.dimensions.image.width = width; - scope.dimensions.image.height = height; - }; //when loading an image without any crop info, we center and fit it - var fitImage = function(){ - fitToViewPort($image); - centerImage($image); - syncOverLay(); - setConstraints($image); - }; - - //utill for centering scaled image - var centerImage = function(img) { - var image_width = img.width(), - image_height = img.height(), - mask_width = $viewport.width(), - mask_height = $viewport.height(); + var resizeImageToEditor = function(){ - img.css({ - 'position': 'absolute', - 'left': scope.dimensions.viewport.width / 2 - scope.dimensions.image.width / 2, - 'top': scope.dimensions.viewport.height / 2 - scope.dimensions.image.height / 2 - }); - }; - - //utill for scaling image to fit viewport - var fitToViewPort = function(img) { //returns size fitting the cropper - var size = calculateAspectRatioFit( + var size = cropperHelper.calculateAspectRatioFit( scope.dimensions.image.width, scope.dimensions.image.height, scope.dimensions.cropper.width, @@ -104,163 +103,154 @@ angular.module("umbraco.directives") true); //sets the image size and updates the scope - setImageSize(size.width, size.height); + scope.dimensions.image.width = size.width; + scope.dimensions.image.height = size.height; - scope.minScale = size.ratio; - scope.maxScale = size.ratio * 3; - scope.currentScale = scope.minScale; - scope.scale = scope.currentScale; + //calculate the best suited ratios + scope.dimensions.scale.min = size.ratio; + scope.dimensions.scale.max = 2; + scope.dimensions.scale.current = size.ratio; + + //center the image + var position = cropperHelper.centerInsideViewPort(scope.dimensions.image, scope.dimensions.cropper); + scope.dimensions.top = position.top; + scope.dimensions.left = position.left; + + setConstraints(); }; - var resizeImageToScale = function(img, ratio){ + //resize to a given ratio + var resizeImageToScale = function(ratio){ //do stuff - var size = calculateSizeToRatio(scope.dimensions.image.originalWidth, scope.dimensions.image.originalHeight, ratio); - - setImageSize(size.width, size.height); - centerImage(img); - scope.currentScale = scope.scale; + var size = cropperHelper.calculateSizeToRatio(scope.dimensions.image.originalWidth, scope.dimensions.image.originalHeight, ratio); + scope.dimensions.image.width = size.width; + scope.dimensions.image.height = size.height; - syncOverLay(); + setConstraints(); + validatePosition(scope.dimensions.image.left, scope.dimensions.image.top); }; - //set constaints for cropping drag and drop - var setConstraints = function(img){ - //do stuff - var w = img.width(), - h = img.height(), - crop_width = $viewport.width() - 2 * 20, - crop_height = $viewport.height() - 2 * 20; + //resize the image to a predefined crop coordinate + var resizeImageToCrop = function(){ + scope.dimensions.image = cropperHelper.convertToStyle( + scope.crop, + {width: scope.dimensions.image.originalWidth, height: scope.dimensions.image.originalHeight}, + scope.dimensions.cropper, + scope.dimensions.margin); - constraints.left.min = 20 + crop_width - w; - constraints.top.min = 20 + crop_height - h; + var ratioCalculation = cropperHelper.calculateAspectRatioFit( + scope.dimensions.image.originalWidth, + scope.dimensions.image.originalHeight, + scope.dimensions.cropper.width, + scope.dimensions.cropper.height, + true); + + scope.dimensions.scale.current = scope.dimensions.image.ratio; + + //min max based on original width/height + scope.dimensions.scale.min = ratioCalculation.ratio; + scope.dimensions.scale.max = 2; }; - //utill for getting either min/max aspect ratio to scale image after - var calculateAspectRatioFit = function(srcWidth, srcHeight, maxWidth, maxHeight, maximize) { - var ratio = [maxWidth / srcWidth, maxHeight / srcHeight ]; - if(maximize){ - ratio = Math.max(ratio[0], ratio[1]); - }else{ - ratio = Math.min(ratio[0], ratio[1]); + + var validatePosition = function(left, top){ + if(left > constraints.left.max) + { + left = constraints.left.max; } - - return { width:srcWidth*ratio, height:srcHeight*ratio, ratio: ratio}; - }; + + if(left <= constraints.left.min){ + left = constraints.left.min; + } + + if(top > constraints.top.max) + { + top = constraints.top.max; + } + if(top <= constraints.top.min){ + top = constraints.top.min; + } + + scope.dimensions.image.left = left; + scope.dimensions.image.top = top; + }; + - //utill for scaling width / height given a ratio - var calculateSizeToRatio= function(srcWidth, srcHeight, ratio) { - return { width:srcWidth*ratio, height:srcHeight*ratio, ratio: ratio}; - }; + + + + + //sets scope.crop to the recalculated % based crop var calculateCropBox = function(){ - scope.crop.left = Math.abs($image[0].offsetLeft - scope.dimensions.margin) / scope.dimensions.image.width; - scope.crop.top = Math.abs($image[0].offsetTop - scope.dimensions.margin) / scope.dimensions.image.height; - - scope.crop.right = 1 - Math.abs(scope.dimensions.cropper.width - (scope.dimensions.image.width - scope.crop.left)) / scope.dimensions.image.width; - scope.crop.bottom = 1 - Math.abs(scope.dimensions.cropper.height - (scope.dimensions.image.height - scope.crop.top)) / scope.dimensions.image.height; - }; - - var calculatePosition = function(crop){ - - var left = (crop.left * scope.dimensions.image.originalWidth); - var top = (crop.top * scope.dimensions.image.originalHeight); - - var cropped_width = scope.dimensions.image.originalWidth - left; - var ratio = cropped_width / scope.dimensions.image.originalWidth; - - scope.scale = ratio; - resizeImageToScale($image, ratio); - - $image.css({ - "top": -top, - "left": -left - }); - - syncOverLay(); - }; - - - - var syncOverLay = function(){ - $overlay.height($image.height()); - $overlay.width($image.width()); - - $overlay.css({ - "top": $image[0].offsetTop, - "left": $image[0].offsetLeft - }); - - calculateCropBox(); + scope.crop = cropperHelper.pixelsToCoordinates(scope.dimensions.image, scope.dimensions.cropper.width, scope.dimensions.cropper.height, scope.dimensions.margin); }; //Drag and drop positioning, using jquery ui draggable var onStartDragPosition, top, left; $overlay.draggable({ - start: function(event, ui) { - syncOverLay(); - }, drag: function(event, ui) { - - - if(ui.position.left <= constraints.left.max && ui.position.left >= constraints.left.min){ - $image.css({ - 'left': ui.position.left - }); - } - - if(ui.position.top <= constraints.top.max && ui.position.top >= constraints.top.min){ - $image.css({ - 'top': ui.position.top - }); - } + scope.$apply(function(){ + validatePosition(ui.position.left, ui.position.top); + }); }, - stop: function() { - syncOverLay(); + stop: function(event, ui){ + scope.$apply(function(){ + calculateCropBox(); + scope.dimensions.image.rnd = Math.random(); + }); } }); - - + var init = function(image){ + scope.loaded = false; + + //set dimensions on image, viewport, cropper etc + setDimensions(image); + + //if we have a crop already position the image + if(scope.crop){ + resizeImageToCrop(); + }else{ + resizeImageToEditor(); + } + + //sets constaints for the cropper + setConstraints(); + + + scope.loaded = true; + }; + + + /// WATCHERS //// + scope.$watchCollection('[width, height]', function(newValues, oldValues){ + //we have to reinit the whole thing if + //one of the external params changes + if(newValues !== oldValues){ + setDimensions($image); + setConstraints(); + } + }); + + //happens when we change the scale + scope.$watch("dimensions.scale.current", function(){ + if(scope.loaded){ + resizeImageToScale(scope.dimensions.scale.current); + calculateCropBox(); + } + }); + + //// INIT ///// $image.load(function(){ $timeout(function(){ - $image.width("auto"); - $image.height("auto"); - - scope.dimensions.image.originalWidth = $image.width(); - scope.dimensions.image.originalHeight = $image.height(); - - setDimensions(); - - if(scope.crop && scope.crop.top){ - calculatePosition(scope.crop); - }else{ - scope.crop = {}; - fitImage(); - } - - scope.loaded = true; + init($image); }); }); - - - /// WATCHERS //// - scope.$watch("scale", function(){ - if(scope.loaded && scope.scale !== scope.currentScale){ - resizeImageToScale($image, scope.scale); - setConstraints($image); - } - }); - - /// WATCHERS //// - scope.$watch("crop", function(newVal, oldVal){ - if(scope.loaded && newVal !== oldVal){ - calculatePosition(scope.crop); - } - }); } }; }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/imaging/umbimagegravity.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/imaging/umbimagegravity.directive.js index 03f3dd7b7e..7a311e5a0e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/imaging/umbimagegravity.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/imaging/umbimagegravity.directive.js @@ -14,104 +14,69 @@ angular.module("umbraco.directives") templateUrl: 'views/directives/imaging/umb-image-gravity.html', scope: { src: '=', - width: "=", - height: "=", - gravity: "=" + center: "=" }, link: function(scope, element, attrs) { + //Internal values for keeping track of the dot and the size of the editor + scope.dimensions = { + width: 0, + height: 0, + left: 0, + top: 0 + }; + //elements var $viewport = element.find(".viewport"); var $image = element.find("img"); var $overlay = element.find(".overlay"); + scope.style = function () { + if(scope.dimensions.width <= 0){ + setDimensions(); + } + + return { + 'top': scope.dimensions.top + 'px', + 'left': scope.dimensions.left + 'px' + }; + }; var setDimensions = function(){ - scope.imagewidth = $image.width(); - scope.imageheight = $image.height(); - }; + scope.dimensions.width = $image.width(); + scope.dimensions.height = $image.height(); - var setImageSize = function(width, height){ - $image.width(width); - $image.height(height); - - $viewport.width(width); - $viewport.height(height); - }; - - var fitImage = function(){ - fitToViewPort($image); - centerImage($image); - $log.log("centered and fitted"); - }; - - //utill for centering scaled image - var centerImage = function(img) { - img.css({ - 'position': 'absolute', - 'left': scope.width / 2 - scope.imageWidth / 2, - 'top': scope.height / 2 - scope.imageHeight / 2 - }); - }; - - //utill for scaling image to fit viewport - var fitToViewPort = function(img) { - //returns size fitting the cropper - var size = calculateAspectRatioFit( - scope.imageWidth, - scope.imageHeight, - scope.width, - scope.height, - false); - - //sets the image size and updates the scope - setImageSize(size.width, size.height); - }; - - - //utill for getting either min/max aspect ratio to scale image after - var calculateAspectRatioFit = function(srcWidth, srcHeight, maxWidth, maxHeight, maximize) { - var ratio = [maxWidth / srcWidth, maxHeight / srcHeight ]; - - if(maximize){ - ratio = Math.max(ratio[0], ratio[1]); - }else{ - ratio = Math.min(ratio[0], ratio[1]); + if(scope.center){ + scope.dimensions.left = scope.center.left * scope.dimensions.width -10; + scope.dimensions.top = scope.center.top * scope.dimensions.height -10; } - - return { width:srcWidth*ratio, height:srcHeight*ratio, ratio: ratio}; - }; - - var calculateGravity = function(){ - scope.gravity.left = $overlay[0].offsetLeft + 10; - scope.gravity.top = $overlay[0].offsetTop + 10; - }; + }; + var calculateGravity = function(){ + scope.dimensions.left = $overlay[0].offsetLeft; + scope.dimensions.top = $overlay[0].offsetTop; + + scope.center.left = (scope.dimensions.left+10) / scope.dimensions.width; + scope.center.top = (scope.dimensions.top+10) / scope.dimensions.height; + }; //Drag and drop positioning, using jquery ui draggable - var onStartDragPosition, top, left; + //TODO ensure that the point doesnt go outside the box $overlay.draggable({ + containment: "parent", stop: function() { - calculateGravity(); + scope.$apply(function(){ + calculateGravity(); + }); } }); - //// INIT ///// $image.load(function(){ - $timeout(function(){ - $image.width("auto"); - $image.height("auto"); - setDimensions(); - fitImage(); - - scope.loaded = true; }); }); - - } }; }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/imaging/umbimagethumbnail.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/imaging/umbimagethumbnail.directive.js index b3ff344224..e748827fb7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/imaging/umbimagethumbnail.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/imaging/umbimagethumbnail.directive.js @@ -7,7 +7,8 @@ * Used by editors that require naming an entity. Shows a textbox/headline with a required validator within it's own form. **/ angular.module("umbraco.directives") - .directive('umbImageThumbnail', function ($timeout, localizationService, $log) { + .directive('umbImageThumbnail', + function ($timeout, localizationService, cropperHelper, $log) { return { restrict: 'E', replace: true, @@ -15,17 +16,69 @@ angular.module("umbraco.directives") scope: { src: '=', - width: '=', - height: '=', - gravity: "=", + width: '@', + height: '@', + center: "=", crop: "=" }, link: function(scope, element, attrs) { - scope.marginLeft = 0-Math.abs( scope.width * scope.gravity.left); - scope.marginTop = 0-Math.abs( scope.width * scope.gravity.top); - + //// INIT ///// + var $image = element.find("img"); + $image.load(function(){ + $timeout(function(){ + $image.width("auto"); + $image.height("auto"); + + scope.image = {}; + scope.image.width = $image[0].width; + scope.image.height = $image[0].height; + + setPreviewStyle(); + }); + }); + + /// WATCHERS //// + scope.$watchCollection('[crop, center]', function(newValues, oldValues){ + //we have to reinit the whole thing if + //one of the external params changes + setPreviewStyle(); + }); + + scope.$watch("center", function(){ + setPreviewStyle(); + }, true); + function setPreviewStyle(){ + if(scope.crop && scope.image){ + scope.preview = cropperHelper.convertToStyle( + scope.crop, + scope.image, + {width: scope.width, height: scope.height}, + 0); + }else if(scope.image){ + + //returns size fitting the cropper + var p = cropperHelper.calculateAspectRatioFit( + scope.image.width, + scope.image.height, + scope.width, + scope.height, + true); + + + if(scope.center){ + var xy = cropperHelper.alignToCoordinates(p, scope.center, {width: scope.width, height: scope.height}); + p.top = xy.top; + p.left = xy.left; + }else{ + + } + + p.position = "absolute"; + scope.preview = p; + } + } } }; }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/services/mediahelperService.mocks.js b/src/Umbraco.Web.UI.Client/src/common/mocks/services/mediahelperService.mocks.js index 61e80b60c9..78196212ac 100644 --- a/src/Umbraco.Web.UI.Client/src/common/mocks/services/mediahelperService.mocks.js +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/services/mediahelperService.mocks.js @@ -19,7 +19,7 @@ function mediaHelper(umbRequestHelper) { * @param {object} options.imageOnly Optional, if true then will only return a path if the media item is an image */ getMediaPropertyValue: function (options) { - return "assets/img/mocks/image.jpg"; + return "assets/img/mocks/big-image.jpg"; }, /** @@ -35,7 +35,7 @@ function mediaHelper(umbRequestHelper) { * @param {object} options.imageModel The media object to retrieve the image path from */ getImagePropertyValue: function (options) { - return "assets/img/mocks/image.jpg"; + return "assets/img/mocks/big-image.jpg"; }, /** * @ngdoc function diff --git a/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js b/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js index e3f86a4b14..4da21ac281 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/assets.service.js @@ -90,17 +90,21 @@ angular.module('umbraco.services') var t = timeout || 5000; var a = attributes || undefined; - yepnope.injectCss(path, function () { - - if (!scope) { - deferred.resolve(true); - }else{ - angularHelper.safeApply(scope, function () { - deferred.resolve(true); - }); - } - - },a,t); + yepnope({ + forceCSS: true, + load: path, + timeout: t, + attrs: a, + callback: function (url, result, key) { + if (!scope) { + deferred.resolve(true); + }else{ + angularHelper.safeApply(scope, function () { + deferred.resolve(true); + }); + } + } + }); return deferred.promise; }, @@ -124,6 +128,26 @@ angular.module('umbraco.services') var t = timeout || 5000; var a = attributes || undefined; + + yepnope({ + forceJS: true, + load: path, + timeout: t, + attrs: a, + callback: function (url, result, key) { + + if (!scope) { + deferred.resolve(true); + }else{ + angularHelper.safeApply(scope, function () { + deferred.resolve(true); + }); + } + } + }); + + +/* yepnope.injectJs(path, function () { if (!scope) { @@ -135,6 +159,7 @@ angular.module('umbraco.services') } },a,t); +*/ return deferred.promise; }, diff --git a/src/Umbraco.Web.UI.Client/src/common/services/cropperhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/cropperhelper.service.js new file mode 100644 index 0000000000..15868c0010 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/services/cropperhelper.service.js @@ -0,0 +1,130 @@ +/** +* @ngdoc service +* @name umbraco.services.mediaHelper +* @description A helper object used for dealing with media items +**/ +function cropperHelper(umbRequestHelper) { + var service = { + //utill for getting either min/max aspect ratio to scale image after + calculateAspectRatioFit : function(srcWidth, srcHeight, maxWidth, maxHeight, maximize) { + var ratio = [maxWidth / srcWidth, maxHeight / srcHeight ]; + + if(maximize){ + ratio = Math.max(ratio[0], ratio[1]); + }else{ + ratio = Math.min(ratio[0], ratio[1]); + } + + return { width:srcWidth*ratio, height:srcHeight*ratio, ratio: ratio}; + }, + + //utill for scaling width / height given a ratio + calculateSizeToRatio : function(srcWidth, srcHeight, ratio) { + return { width:srcWidth*ratio, height:srcHeight*ratio, ratio: ratio}; + }, + + //returns a ng-style object with top,left,width,height pixel measurements + //expects {left,right,top,bottom} - {width,height}, {width,height}, int + //offset is just to push the image position a number of pixels from top,left + convertToStyle : function(coordinates, originalSize, viewPort, offset){ + + var coordinates_px = service.coordinatesToPixels(coordinates, originalSize, offset); + var _offset = offset || 0; + + var x = coordinates.x1 + Math.abs(coordinates.x2); + var left_of_x = originalSize.width - (originalSize.width * x); + var ratio = viewPort.width / left_of_x; + + var style = { + position: "absolute", + top: -(coordinates_px.y1*ratio)+ _offset, + left: -(coordinates_px.x1* ratio)+ _offset, + width: Math.floor(originalSize.width * ratio), + height: Math.floor(originalSize.height * ratio), + originalWidth: originalSize.width, + originalHeight: originalSize.height, + ratio: ratio + }; + + return style; + }, + + //returns a ng-style object with top,left,width,height pixel measurements + //expects {left,right,top,bottom} - {width,height}, {width,height}, int + //offset is just to push the image position a number of pixels from top,left + coordinatesToPixels : function(coordinates, originalSize, offset){ + + var coordinates_px = { + x1: Math.floor(coordinates.x1 * originalSize.width), + y1: Math.floor(coordinates.y1 * originalSize.height), + x2: Math.floor(coordinates.x2 * originalSize.width), + y2: Math.floor(coordinates.y2 * originalSize.height) + }; + + return coordinates_px; + }, + + pixelsToCoordinates : function(image, width, height, offset){ + + var x1_px = Math.abs(image.left-offset); + var y1_px = Math.abs(image.top-offset); + + var x2_px = (x1_px + width) - image.width; + var y2_px = (y1_px + height) - image.height; + + //crop coordinates in % + var crop = {}; + crop.x1 = x1_px / image.width; + crop.y1 = y1_px / image.height; + crop.x2 = x2_px / image.width; + crop.y2 = y2_px / image.height; + + return crop; + }, + + centerInsideViewPort : function(img, viewport){ + var left = viewport.width/ 2 - img.width / 2, + top = viewport.height / 2 - img.height / 2; + + return {left: left, top: top}; + }, + + alignToCoordinates : function(image, center, viewport){ + + var min_left = (image.width) - (viewport.width); + var min_top = (image.height) - (viewport.height); + + var c_top = -(center.top * image.height) + (viewport.height / 2); + var c_left = -(center.left * image.width) + (viewport.width / 2); + + if(c_top < -min_top){ + c_top = -min_top; + } + if(c_top > 0){ + c_top = 0; + } + if(c_left < -min_left){ + c_left = -min_left; + } + if(c_left > 0){ + c_left = 0; + } + return {left: c_left, top: c_top}; + }, + + + syncElements : function(source, target){ + target.height(source.height()); + target.width(source.width()); + + target.css({ + "top": source[0].offsetTop, + "left": source[0].offsetLeft + }); + } + }; + + return service; +} + +angular.module('umbraco.services').factory('cropperHelper', cropperHelper); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js index aa253a9275..6b80f6580d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/mediahelper.service.js @@ -41,7 +41,7 @@ function mediaHelper(umbRequestHelper) { //this performs a simple check to see if we have a media file as value //it doesnt catch everything, but better then nothing - if (item.value.indexOf(mediaRoot) === 0) { + if (angular.isString(item.value) && item.value.indexOf(mediaRoot) === 0) { return true; } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js index 0b416a2753..64e4fa222b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/util.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/util.service.js @@ -1,20 +1,20 @@ /*Contains multiple services for various helper tasks */ -function packageHelper(assetsService, treeService, eventsService) { +function packageHelper(assetsService, treeService, eventsService, $templateCache) { return { /** Called when a package is installed, this resets a bunch of data and ensures the new package assets are loaded in */ packageInstalled: function () { - //assetsService._reloadApplicationAssets().then(function() { - // treeService.clearCache(); - // //send event - // //eventsService.emit("app.reInitialize"); - // //TODO: This doesn't work and will end in an infinite browser load loop, we can't really - // // re-bootstrap anyways since that would be the same as loading the whole browser window. - // //angular.bootstrap(document, ['umbraco']); - //}); + //clears the tree + treeService.clearCache(); + + //clears the template cache + $templateCache.removeAll(); + + //emit event to notify anything else + eventsService.emit("app.reInitialize"); } }; diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index 30a89fe63d..acb14f6a91 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -48,6 +48,7 @@ @import "../../lib/bootstrap/less/modals.less"; @import "../../lib/bootstrap/less/tooltip.less"; @import "../../lib/bootstrap/less/popovers.less"; +@import "tipmenu.less"; // Components: Misc @import "../../lib/bootstrap/less/thumbnails.less"; diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less index fd37de89b8..17a0116b00 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -130,6 +130,8 @@ ul.color-picker li a { text-decoration: none; } + + .umb-thumbnails{ position: relative; } @@ -194,13 +196,17 @@ ul.color-picker li a { // Cropper // ------------------------------------------------- +.umb-cropper{ + position: relative; + padding-bottom: 30px; +} + .umb-cropper img, .umb-cropper-gravity img{ position: absolute; top: 0; left: 0; } - .umb-cropper .overlay, .umb-cropper-gravity .overlay { top: 0; left: 0; @@ -208,13 +214,19 @@ ul.color-picker li a { z-index: 6001; } -.umb-cropper .viewport, .umb-cropper-gravity .viewport { - overflow: hidden; - position: relative; - border:1px solid @grayLight; - width: 600px; - height: 480px; +.umb-cropper .viewport{ + overflow: hidden; + position: relative; + border: 1px solid @grayLight; } + +.umb-cropper-gravity .viewport{ + overflow: hidden; + position: relative; + width: 400px; + height: 300px +} + .umb-cropper .viewport:after { content: ""; @@ -227,9 +239,9 @@ ul.color-picker li a { -moz-opacity: .75; opacity: .75; filter: alpha(opacity=7); - -webkit-box-shadow: inset 0 0 0 20px white,inset 0 0 0 21px rgba(0,0,0,.1),inset 0 0 20px 21px rgba(0,0,0,.2); - -moz-box-shadow: inset 0 0 0 20px white,inset 0 0 0 21px rgba(0,0,0,.1),inset 0 0 20px 21px rgba(0,0,0,.2); - box-shadow: inset 0 0 0 20px white,inset 0 0 0 21px rgba(0,0,0,.1),inset 0 0 20px 21px rgba(0,0,0,.2); + -webkit-box-shadow: inset 0 0 0 40px white,inset 0 0 0 41px rgba(0,0,0,.1),inset 0 0 40px 41px rgba(0,0,0,.2); + -moz-box-shadow: inset 0 0 0 40px white,inset 0 0 0 41px rgba(0,0,0,.1),inset 0 0 40px 41px rgba(0,0,0,.2); + box-shadow: inset 0 0 0 40px white,inset 0 0 0 41px rgba(0,0,0,.1),inset 0 0 40px 41px rgba(0,0,0,.2); } .umb-cropper-gravity .overlay{ @@ -240,12 +252,27 @@ ul.color-picker li a { opacity: 0.6; background: white; } + .umb-cropper-gravity .overlay i{ font-size: 26px; line-height: 26px; opacity: 0.8 !important; } +.umb-cropper .crop-container{ + text-align: center; +} + +.umb-cropper .crop-slider{ + vertical-align: middle; + width: 250px; + padding-top: 12px; +} + +.umb-cropper .crop-slider i{color: @gray;} +.umb-cropper .crop-slider input{ + margin-top: 7px; +} // diff --git a/src/Umbraco.Web.UI.Client/src/less/tipmenu.less b/src/Umbraco.Web.UI.Client/src/less/tipmenu.less new file mode 100644 index 0000000000..c1c5059319 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/tipmenu.less @@ -0,0 +1,15 @@ +.tipmenu .tooltip{ + position:absolute; + left:0; + right:0; + margin-left:auto; + margin-right:auto; +} + +.tipmenu:hover .tooltip{ + opacity: 1; +} + +.tipmenu:hover .tooltip a{ + color: white; +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/loader.js b/src/Umbraco.Web.UI.Client/src/loader.js index ffb61665a5..9695ae0b59 100644 --- a/src/Umbraco.Web.UI.Client/src/loader.js +++ b/src/Umbraco.Web.UI.Client/src/loader.js @@ -1,16 +1,24 @@ +//global no-cache filter, this is enabled when we're in debug mode +//in live mode we use client dependency and don't turn this thing on +yepnope.addFilter(function (resourceObj) { + var url = resourceObj.url; + if(url.indexOf("lib/") === 0 || url.indexOf("js/umbraco.") === 0){ + return resourceObj; + } + + resourceObj.url = resourceObj.url + "?umb__rnd=" + (new Date).getTime(); + return resourceObj; +}); + + yepnope({ load: [ 'lib/jquery/jquery-2.0.3.min.js', /* the jquery ui elements we need */ - /* NOTE: I've opted not to use the full lib, just the parts we need to save on DL*/ - 'lib/jquery/jquery.ui.core.min.js', - 'lib/jquery/jquery.ui.widget.min.js', - - 'lib/jquery/jquery.ui.mouse.min.js', - 'lib/jquery/jquery.ui.sortable.min.js', - + 'lib/jquery/jquery-ui-1.10.3.custom.min.js', + /* 1.1.5 */ 'lib/angular/1.1.5/angular.min.js', 'lib/angular/1.1.5/angular-cookies.min.js', diff --git a/src/Umbraco.Web.UI.Client/src/views/directives/imaging/umb-image-crop.html b/src/Umbraco.Web.UI.Client/src/views/directives/imaging/umb-image-crop.html index 82f83d56d6..953627637f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/directives/imaging/umb-image-crop.html +++ b/src/Umbraco.Web.UI.Client/src/views/directives/imaging/umb-image-crop.html @@ -1,32 +1,21 @@ -
+
+
- -
+ +
- - -
-		{{dimensions | json}}
-
- -
-		{{box | json}}
-
- - +
+ + + + +
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/directives/imaging/umb-image-gravity.html b/src/Umbraco.Web.UI.Client/src/views/directives/imaging/umb-image-gravity.html index 198e8d3f98..cdceab1335 100644 --- a/src/Umbraco.Web.UI.Client/src/views/directives/imaging/umb-image-gravity.html +++ b/src/Umbraco.Web.UI.Client/src/views/directives/imaging/umb-image-gravity.html @@ -1,12 +1,12 @@
-
- +
+ -
+
+
-
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/directives/imaging/umb-image-thumbnail.html b/src/Umbraco.Web.UI.Client/src/views/directives/imaging/umb-image-thumbnail.html index fe7e299648..765787b48e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/directives/imaging/umb-image-thumbnail.html +++ b/src/Umbraco.Web.UI.Client/src/views/directives/imaging/umb-image-thumbnail.html @@ -1,5 +1,4 @@
- + ng-style="{height: height, width: width, overflow: 'hidden', position: 'relative'}"> +
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.controller.js new file mode 100644 index 0000000000..68b99efd33 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.controller.js @@ -0,0 +1,57 @@ +//this controller simply tells the dialogs service to open a mediaPicker window +//with a specified callback, this callback will receive an object with a selection on it +angular.module('umbraco').controller("Umbraco.PropertyEditors.ImageCropperController", + function($rootScope, $scope, mediaHelper, $timeout, editorState) { + + //check the pre-values for multi-picker + var multiPicker = $scope.model.config.multiPicker !== '0' ? true : false; + + //used to reference the part of the data we will either crop or "point" + $scope.currentCrop = undefined; + $scope.currentPoint = undefined; + + var imgPath = mediaHelper.getImagePropertyValue({imageModel: editorState.current}); + + $scope.crop = function(crop){ + $scope.currentCrop = crop; + $scope.currentPoint = undefined; + }; + + $scope.done = function(){ + $scope.currentCrop = undefined; + $scope.currentPoint = undefined; + }; + + + //Data sample + if(!$scope.model.value){ + $scope.model.value = { + //image to crops + src: imgPath, + + //global intrestpoint, used if no crop is specified + center: {left: 0.5, top: 0.4}, + crops:{ + thumbnail: + { + //crop dimensions + width: 100, + height: 100, + + //crops in percentages + crop:{ + "x1": 0.31731772342645215, + "y1": 0.17420325244997603, + "x2": -0.36246473116627076, + "y2": -0.30226197981593617 + } + }, + highrise: + { + width: 90, + height: 340 + } + } + }; + } +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.html new file mode 100644 index 0000000000..6081e9fb6e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.html @@ -0,0 +1,46 @@ +
+ +
+
+
+
+ +
+ + Done +
+ +
+ +
+
+ + +
+
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js index 7ecf0a4b9a..5a0cdc9fc9 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js @@ -11,7 +11,7 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl //Data sample var __img = { //image to crop - src: "assets/img/mocks/image.jpg", + src: "assets/img/mocks/big-image.jpg", //global gravity, used if not crop is specified gravity: {left: 0.5, top: 0.4}, @@ -20,8 +20,8 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl thumbnail: { //crop dimensions - width: "30px", - height: "40px", + width: 100, + height: 100, //crops in percentages crop:{ "left": 0.31731772342645215, @@ -33,8 +33,8 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl banner: { - width: "200px", - height: "20px", + width: 340, + height: 90, crop:{ "left": 0.31731772342645215, "top": 0.17420325244997603, @@ -45,8 +45,8 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl highrise: { - width: "20px", - height: "200px" + width: 90, + height: 340 }, } }; @@ -81,7 +81,6 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl $scope.edit = function(image){ $scope.currentImage = image; - $scope.cropper.image = __img; } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html index 9e31aadddb..b95b4a6258 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html @@ -1,35 +1,38 @@
-
- +
-
+
+ height="200" + width= "380" + src="cropper.image.src" + gravity="cropper.point.gravity" />
  • + ng-click="cropper.crop = undefined; cropper.point = cropper.image" /> + original
  • -
  • - + +
  • + + height="value.height" + width="value.width">
  • @@ -38,7 +41,7 @@
    • - + diff --git a/src/Umbraco.Web.UI/Properties/Settings.Designer.cs b/src/Umbraco.Web.UI/Properties/Settings.Designer.cs index 5fd974b641..962354c575 100644 --- a/src/Umbraco.Web.UI/Properties/Settings.Designer.cs +++ b/src/Umbraco.Web.UI/Properties/Settings.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.34003 +// Runtime Version:4.0.30319.18408 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -22,5 +22,14 @@ namespace Umbraco.Web.UI.Properties { return defaultInstance; } } + + [global::System.Configuration.ApplicationScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("http://our.umbraco.org/umbraco/webservices/api/repository.asmx")] + public string Umbraco_Web_UI_org_umbraco_our_Repository { + get { + return ((string)(this["Umbraco_Web_UI_org_umbraco_our_Repository"])); + } + } } } diff --git a/src/Umbraco.Web.UI/Properties/Settings.settings b/src/Umbraco.Web.UI/Properties/Settings.settings index 2bd17f050c..5df40e9fea 100644 --- a/src/Umbraco.Web.UI/Properties/Settings.settings +++ b/src/Umbraco.Web.UI/Properties/Settings.settings @@ -1,5 +1,9 @@  - + - + + + http://our.umbraco.org/umbraco/webservices/api/repository.asmx + + \ No newline at end of file diff --git a/src/Umbraco.Web.UI/umbraco/Views/Default.cshtml b/src/Umbraco.Web.UI/umbraco/Views/Default.cshtml index 48925499d1..1f036c8609 100644 --- a/src/Umbraco.Web.UI/umbraco/Views/Default.cshtml +++ b/src/Umbraco.Web.UI/umbraco/Views/Default.cshtml @@ -5,10 +5,9 @@ @using Umbraco.Web @using Umbraco.Web.Editors @using umbraco + @inherits System.Web.Mvc.WebViewPage -@{ - Layout = null; -} + @@ -56,7 +55,7 @@ @*And finally we can load in our angular app*@ - + diff --git a/src/Umbraco.Web.UI/umbraco/developer/Packages/installer.aspx b/src/Umbraco.Web.UI/umbraco/developer/Packages/installer.aspx index 0aae25a974..1f0c4a873e 100644 --- a/src/Umbraco.Web.UI/umbraco/developer/Packages/installer.aspx +++ b/src/Umbraco.Web.UI/umbraco/developer/Packages/installer.aspx @@ -28,6 +28,7 @@ + @@ -35,6 +36,7 @@ +

      @@ -94,6 +96,7 @@ +
      @@ -103,6 +106,7 @@ notifies you the install is completed.

      + @@ -121,8 +125,8 @@
      -

      - Binary files in the package!

      +

      Binary files in the package!

      + Read more...

      @@ -140,6 +144,7 @@

      +
      @@ -158,6 +163,7 @@

+

@@ -198,6 +204,7 @@

+

@@ -219,6 +226,7 @@

+

@@ -240,6 +248,7 @@

+