Merge branch '7.0.1' of https://github.com/umbraco/Umbraco-CMS into 7.0.1

This commit is contained in:
perploug
2013-12-11 09:44:37 +01:00
18 changed files with 362 additions and 204 deletions

View File

@@ -6,124 +6,184 @@
angular.module("umbraco.directives.html")
.directive('umbPhotoFolder', function ($compile, $log, $timeout, $filter, imageHelper, umbRequestHelper) {
function renderCollection(scope, photos, fixedRowWidth) {
/** sets the image's url - will check if it is a folder or a real image */
function setImageUrl(img) {
//get the image property (if one exists)
var imageProp = imageHelper.getImagePropertyValue({ imageModel: img });
if (!imageProp) {
img.thumbnail = "none";
}
else {
//get the proxy url for big thumbnails (this ensures one is always generated)
var thumbnailUrl = umbRequestHelper.getApiUrl(
"imagesApiBaseUrl",
"GetBigThumbnail",
[{ mediaId: img.id }]);
img.thumbnail = thumbnailUrl;
}
}
/** sets the images original size properties - will check if it is a folder and if so will just make it square */
function setOriginalSize(img, maxHeight) {
//set to a square by default
img.originalWidth = maxHeight;
img.originalHeight = maxHeight;
var widthProp = _.find(img.properties, function (v) { return (v.alias === "umbracoWidth"); });
if (widthProp && widthProp.value) {
img.originalWidth = parseInt(widthProp.value, 10);
if (isNaN(img.originalWidth)) {
img.originalWidth = maxHeight;
}
}
var heightProp = _.find(img.properties, function (v) { return (v.alias === "umbracoHeight"); });
if (heightProp && heightProp.value) {
img.originalHeight = parseInt(heightProp.value, 10);
if (isNaN(img.originalHeight)) {
img.originalHeight = maxHeight;
}
}
}
/** sets the image style which get's used in the angular markup */
function setImageStyle(img, width, height, rightMargin, bottomMargin) {
img.style = { width: width, height: height, "margin-right": rightMargin + "px", "margin-bottom": bottomMargin + "px" };
img.thumbStyle = {
"background-image": "url('" + img.thumbnail + "')",
"background-repeat": "no-repeat",
"background-position": "center",
"background-size": Math.min(width, img.originalWidth) + "px " + Math.min(height, img.originalHeight) + "px"
};
}
/** gets the image's scaled wdith based on the max row height and width */
function getScaledWidth(img, maxHeight, maxRowWidth) {
var scaled = img.originalWidth * maxHeight / img.originalHeight;
//round down, we don't want it too big even by half a pixel otherwise it'll drop to the next row
var rounded = Math.floor(scaled);
//if the max row width is smaller than the scaled width of the image then we better make
// the scaled width the max row width so an image can actually fit on the row
return Math.min(rounded, maxRowWidth);
}
/**
This will determine the row/image height for the next collection of images which takes into account the
ideal image count per row. It will check if a row can be filled with this ideal count and if not - if there
are additional images available to fill the row it will keep calculating until they fit.
*/
function getRowHeight(imgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow, margin) {
var currRowWidth = 0;
var idealImages = imgs.slice(0, idealImgPerRow);
//take into account the margin, we will have 1 less margin item than we have total images
// easiest to just reduce the maxRowWidth by that number
var targetRowWidth = maxRowWidth - ((idealImages.length -1) * margin);
var maxScaleableHeight = getMaxScaleableHeight(idealImages, maxRowHeight);
var targetHeight = Math.max(maxScaleableHeight, minDisplayHeight);
for (var i = 0; i < idealImages.length; i++) {
currRowWidth += getScaledWidth(idealImages[i], targetHeight, targetRowWidth);
}
if (currRowWidth > targetRowWidth) {
//get the ratio
var r = targetRowWidth / currRowWidth;
var newHeight = targetHeight * r;
//if this becomes smaller than the min display then we need to use the min display,
if (newHeight < minDisplayHeight) {
newHeight = minDisplayHeight;
}
//always take the rounded down width so we know it will fit
return Math.floor(newHeight);
}
else {
//we know the width will fit in a row, but we now need to figure out if we can fill
// the entire row in the case that we have more images remaining than the idealImgPerRow.
if (idealImages.length === imgs.length) {
//we have no more remaining images to fill the space, so we'll just use the calc height
return targetHeight;
}
else {
//we have additional images so we'll recurse and add 1 to the idealImgPerRow until it fits
return getRowHeight(imgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow + 1, margin);
}
}
}
/** builds an image grid row */
function buildRow(imgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow, margin) {
var currRowWidth = 0;
var row = [];
var calcRowHeight = getRowHeight(imgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow, margin);
var sizes = [];
for (var i = 0; i < imgs.length; i++) {
var scaledWidth = getScaledWidth(imgs[i], calcRowHeight, maxRowWidth);
if (currRowWidth + scaledWidth <= maxRowWidth) {
currRowWidth += scaledWidth;
sizes.push({ width: scaledWidth, height: calcRowHeight });
row.push(imgs[i]);
}
else {
//the max width has been reached
break;
}
}
//loop through the images for the row and apply the styles
for (var j = 0; j < row.length; j++) {
var bottomMargin = margin;
//make the margin 0 for the last one
if (j === (row.length - 1)) {
margin = 0;
}
setImageStyle(row[j], sizes[j].width, sizes[j].height, margin, bottomMargin);
}
return row;
}
/** Returns the maximum image scaling height for the current image collection */
function getMaxScaleableHeight(imgs, maxRowHeight) {
var smallestHeight = _.min(imgs, function (item) { return item.originalHeight; }).originalHeight;
//adjust the smallestHeight if it is larger than the static max row height
if (smallestHeight > maxRowHeight) {
smallestHeight = maxRowHeight;
}
return smallestHeight;
}
/** Creates the image grid with calculated widths/heights for images to fill the grid nicely */
function buildGrid(images, maxRowWidth, maxRowHeight, startingIndex, minDisplayHeight, idealImgPerRow, margin) {
var rows = [];
var imagesProcessed = 0;
//first fill in all of the original image sizes and URLs
for (var i = startingIndex; i < images.length; i++) {
setImageUrl(images[i]);
setOriginalSize(images[i], maxRowHeight);
}
// initial height - effectively the maximum height +/- 10%;
var initialHeight = Math.max(scope.minHeight, Math.floor(fixedRowWidth / 5));
// store relative widths of all images (scaled to match estimate height above)
var ws = [];
$.each(photos, function (key, val) {
val.width_n = $.grep(val.properties, function (v, index) { return (v.alias === "umbracoWidth"); })[0];
val.height_n = $.grep(val.properties, function (v, index) { return (v.alias === "umbracoHeight"); })[0];
if (val.width_n && val.height_n) {
var parsedWidth = parseInt(val.width_n.value, 10);
var parsedHeight = parseInt(val.height_n.value, 10);
//if the parsedHeight is less than the minHeight than set it to the minHeight
//TODO: Should we set it to it's original in this case?
if (parsedHeight >= scope.minHeight) {
if (parsedHeight !== initialHeight) {
parsedWidth = Math.floor(parsedWidth * (initialHeight / parsedHeight));
}
ws.push(parsedWidth);
}
else {
ws.push(scope.minHeight);
}
} else {
//if its files or folders, we make them square
ws.push(scope.minHeight);
}
});
var rowNum = 0;
var limit = photos.length;
while (scope.baseline < limit) {
rowNum++;
// number of images appearing in this row
var imgsPerRow = 0;
// total width of images in this row - including margins
var totalRowWidth = 0;
// calculate width of images and number of images to view in this row.
while ((totalRowWidth * 1.1 < fixedRowWidth) && (scope.baseline + imgsPerRow < limit)) {
totalRowWidth += ws[scope.baseline + imgsPerRow++] + scope.border * 2;
}
// Ratio of actual width of row to total width of images to be used.
var r = fixedRowWidth / totalRowWidth;
// image number being processed
var i = 0;
// reset total width to be total width of processed images
totalRowWidth = 0;
// new height is not original height * ratio
var ht = Math.floor(initialHeight * r);
var row = {};
row.photos = [];
row.style = {};
row.style = { "height": ht + scope.border * 2, "width": fixedRowWidth };
rows.push(row);
while (i < imgsPerRow) {
var photo = photos[scope.baseline + i];
// Calculate new width based on ratio
var calcWidth = Math.floor(ws[scope.baseline + i] * r);
// add to total width with margins
totalRowWidth += calcWidth + scope.border * 2;
//get the image property (if one exists)
var imageProp = imageHelper.getImagePropertyValue({ imageModel: photo });
if (!imageProp) {
//TODO: Do something better than this!!
photo.thumbnail = "none";
}
else {
//get the proxy url for big thumbnails (this ensures one is always generated)
var thumbnailUrl = umbRequestHelper.getApiUrl(
"imagesApiBaseUrl",
"GetBigThumbnail",
[{ mediaId: photo.id }]);
photo.thumbnail = thumbnailUrl;
}
photo.style = { "width": calcWidth, "height": ht, "margin": scope.border + "px", "cursor": "pointer" };
row.photos.push(photo);
i++;
}
// set row height to actual height + margins
scope.baseline += imgsPerRow;
// if total width is slightly smaller than
// actual div width then add 1 to each
// photo width till they match
while ((imagesProcessed + startingIndex) < images.length) {
//get the maxHeight for the current un-processed images
var currImgs = images.slice(imagesProcessed);
/*i = 0;
while (tw < w-1) {
row.photos[i].style.width++;
i = (i + 1) % c;
tw++;
}*/
// if total width is slightly bigger than
// actual div width then subtract 1 from each
// photo width till they match
i = 0;
while (totalRowWidth > fixedRowWidth - 1) {
row.photos[i].style.width--;
i = (i + 1) % imgsPerRow;
totalRowWidth--;
//build the row
var row = buildRow(currImgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow, margin);
if (row.length > 0) {
rows.push(row);
imagesProcessed += row.length;
}
else {
//if there was nothing processed, exit
break;
}
}
@@ -144,28 +204,34 @@ angular.module("umbraco.directives.html")
$timeout(function () {
var photos = ngModel.$modelValue;
scope.imagesOnly = element.attr('imagesOnly');
scope.baseline = element.attr('baseline') ? parseInt(element.attr('baseline'), 10) : 0;
scope.minWidth = element.attr('min-width') ? parseInt(element.attr('min-width'), 10) : 420;
scope.minHeight = element.attr('min-height') ? parseInt(element.attr('min-height'), 10) : 200;
scope.border = element.attr('border') ? parseInt(element.attr('border'), 10) : 5;
scope.clickHandler = scope.$eval(element.attr('on-click'));
var fixedRowWidth = Math.max(element.width(), scope.minWidth);
scope.rows = renderCollection(scope, photos, fixedRowWidth);
//todo: this doesn't do anything
var imagesOnly = element.attr('imagesOnly');
var margin = element.attr('border') ? parseInt(element.attr('border'), 10) : 5;
var startingIndex = element.attr('baseline') ? parseInt(element.attr('baseline'), 10) : 0;
var minWidth = element.attr('min-width') ? parseInt(element.attr('min-width'), 10) : 420;
var minHeight = element.attr('min-height') ? parseInt(element.attr('min-height'), 10) : 100;
var maxHeight = element.attr('max-height') ? parseInt(element.attr('max-height'), 10) : 300;
var idealImgPerRow = element.attr('ideal-items-per-row') ? parseInt(element.attr('ideal-items-per-row'), 10) : 5;
var fixedRowWidth = Math.max(element.width(), minWidth);
scope.rows = buildGrid(photos, fixedRowWidth, maxHeight, startingIndex, minHeight, idealImgPerRow, margin);
if (attrs.filterBy) {
scope.$watch(attrs.filterBy, function (newVal, oldVal) {
if (newVal !== oldVal) {
var p = $filter('filter')(photos, newVal, false);
scope.baseline = 0;
var m = renderCollection(scope, p);
var m = buildGrid(p, fixedRowWidth, 400);
scope.rows = m;
}
});
}
}, 200); //end timeout
}, 500); //end timeout
} //end if modelValue
}; //end $render

View File

@@ -21,8 +21,10 @@ function authResource($q, $http, umbRequestHelper, angularHelper) {
$http.post(
umbRequestHelper.getApiUrl(
"authenticationApiBaseUrl",
"PostLogin",
[{ username: username }, { password: password }])),
"PostLogin"), {
username: username,
password: password
}),
'Login failed for user ' + username);
},

View File

@@ -42,7 +42,8 @@ function appState(eventsService) {
touchDevice: null,
showTray: null,
stickyNavigation: null,
navMode: null
navMode: null,
isReady: null
};
var sectionState = {

View File

@@ -11,31 +11,19 @@ app.run(['userService', '$log', '$rootScope', '$location', 'navigationService',
xhr.setRequestHeader("X-XSRF-TOKEN", $cookies["XSRF-TOKEN"]);
}
});
var firstRun = true;
/** Listens for authentication and checks if our required assets are loaded, if/once they are we'll broadcast a ready event */
eventsService.on("app.authenticated", function(evt, data) {
assetsService._loadInitAssets().then(function() {
appState.setGlobalState("isReady", true);
//send the ready event
eventsService.emit("app.ready", data);
});
});
/** when we have a successful first route that is not the login page - *meaning the user is authenticated*
we'll get the current user from the user service and ensure it broadcasts it's events. If the route
is successful from after a login then this will not actually do anything since the authenticated event would
have alraedy fired, but if the user is loading the angularjs app for the first time and they are already authenticated
then this is when the authenticated event will be fired.
*/
/** execute code on each successful route */
$rootScope.$on('$routeChangeSuccess', function(event, current, previous) {
if (firstRun && !$location.url().toLowerCase().startsWith("/login")) {
userService.getCurrentUser({ broadcastEvent: true }).then(function (user) {
firstRun = false;
});
}
if(current.params.section){
$rootScope.locationTitle = current.params.section + " - " + $location.$$host;
}

View File

@@ -243,8 +243,23 @@ ul.color-picker li a {
position: relative;
}
.umb-photo-folder .picrow div a {
display: inline-block;
.umb-photo-folder .picrow div.pic {
margin: 0px;
padding: 0px;
border: none;
display: inline-block;
overflow: hidden;
}
.umb-photo-folder .picrow div a:first-child {
width:100%;
height:100%;
}
.umb-photo-folder .picrow div.umb-photo {
width:100%;
height:100%;
background-color: @grayLighter;
}
.umb-photo-folder a:hover{text-decoration: none}
@@ -258,7 +273,7 @@ ul.color-picker li a {
}
//this is a temp hack, to provide selectors in the dialog:
.umb-dialogs-mediapicker .umb-photo-folder .pic:hover .selector-overlay{
.umb-photo-folder .pic:hover .selector-overlay{
position: absolute;
bottom: 0px;
left: 0px;

View File

@@ -1,5 +1,5 @@
app.config(function ($routeProvider) {
/** This checks if the user is authenticated for a route and what the isRequired is set to.
Depending on whether isRequired = true, it first check if the user is authenticated and will resolve successfully
otherwise the route will fail and the $routeChangeError event will execute, in that handler we will redirect to the rejected
@@ -8,7 +8,7 @@ app.config(function ($routeProvider) {
return {
/** Checks that the user is authenticated, then ensures that are requires assets are loaded */
isAuthenticatedAndReady: function ($q, userService, $route, assetsService) {
isAuthenticatedAndReady: function ($q, userService, $route, assetsService, appState) {
var deferred = $q.defer();
//don't need to check if we've redirected to login and we've already checked auth
@@ -21,14 +21,33 @@ app.config(function ($routeProvider) {
.then(function () {
assetsService._loadInitAssets().then(function() {
//is auth, check if we allow or reject
if (isRequired) {
//this will resolve successfully so the route will continue
deferred.resolve(true);
//This could be the first time has loaded after the user has logged in, in this case
// we need to broadcast the authenticated event - this will be handled by the startup (init)
// handler to set/broadcast the ready state
if (appState.getGlobalState("isReady") !== true) {
userService.getCurrentUser({ broadcastEvent: true }).then(function(user) {
//is auth, check if we allow or reject
if (isRequired) {
//this will resolve successfully so the route will continue
deferred.resolve(true);
}
else {
deferred.reject({ path: "/" });
}
});
}
else {
deferred.reject({ path: "/" });
//is auth, check if we allow or reject
if (isRequired) {
//this will resolve successfully so the route will continue
deferred.resolve(true);
}
else {
deferred.reject({ path: "/" });
}
}
});
}, function () {

View File

@@ -1,11 +1,10 @@
<div class="umb-photo-folder">
<div class="picrow" ng-repeat="row in rows" ng-style="row.style">
<div class="picrow" ng-repeat="row in rows">
<div class="pic"
style="margin: 0px; padding: 0px; border: none; display: inline-block; overflow: hidden"
ng-style="img.style" ng-repeat="img in row.photos">
<a
href="#media/media/edit/{{img.id}}"
<div class="pic"
ng-style="img.style" ng-repeat="img in row">
<a href="#media/media/edit/{{img.id}}"
ng-click="clickHandler(img, $event, false)"
ng-switch="img.thumbnail"
title="{{img.name}}">
@@ -15,17 +14,14 @@
{{img.name}}
</div>
<img class="umb-photo"
ng-switch-default
ng-src="{{img.thumbnail}}"
ng-style="img.style"
alt="{{img.name}}" />
<div ng-switch-default class="umb-photo" ng-style="img.thumbStyle" alt="{{img.name}}"></div>
</a>
<a href ng-click="clickHandler(img, $event, true)" ng-show="img.contentTypeAlias === 'Folder'" class="selector-overlay">Select
</a>
<div>
</div>
</a>
</div>
</div>

View File

@@ -1,28 +1,29 @@
<form ng-controller="Umbraco.PropertyEditors.FolderBrowserController" id="fileupload"
style="width: 100%"
method="POST" enctype="multipart/form-data"
class="umb-editor umb-folderbrowser"
data-file-upload="options"
data-file-upload-progress=""
ng-hide="creating"
data-ng-class="{'fileupload-processing': processing() || loadingFiles}">
<span class="fileinput-button umb-upload-button-big"
ng-hide="dropping"
ng-class="{disabled: disabled}">
<i class="icon icon-page-up"></i>
<p>Click to upload</p>
<input type="file" name="files[]" multiple ng-disabled="disabled">
</span>
<form ng-controller="Umbraco.PropertyEditors.FolderBrowserController" id="fileupload"
style="width: 100%"
method="POST" enctype="multipart/form-data"
class="umb-editor umb-folderbrowser"
data-file-upload="options"
data-file-upload-progress=""
ng-hide="creating"
data-ng-class="{'fileupload-processing': processing() || loadingFiles}">
<umb-upload-dropzone dropping="dropping" files="filesUploading">
<span class="fileinput-button umb-upload-button-big"
style="margin-bottom: 5px;"
ng-hide="dropping"
ng-class="{disabled: disabled}">
<i class="icon icon-page-up"></i>
<p>Click to upload</p>
<input type="file" name="files[]" multiple ng-disabled="disabled">
</span>
<umb-upload-dropzone dropping="dropping" files="filesUploading">
</umb-upload-dropzone>
<umb-photo-folder
min-height="100"
min-width="100"
on-click="clickHandler"
ng-model="images"/>
</form>
<umb-photo-folder
min-height="100"
min-width="220"
on-click="clickHandler"
ng-model="images" />
</form>