From d61b9bfdd76320a7cd7553da3fd08daeac1dd1f8 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 11 Dec 2013 14:34:04 +1100 Subject: [PATCH] Awesome progress on the photo folder changes to have correctly calculated images sizes - now just need to get margins going and centering of images that are smaller than the min. --- .../html/umbphotofolder.directive.js | 300 +++++++++++------- .../src/less/property-editors.less | 9 +- .../directives/html/umb-photo-folder.html | 15 +- .../folderbrowser/folderbrowser.html | 2 +- .../config/ClientDependency.config | 2 +- 5 files changed, 194 insertions(+), 134 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/html/umbphotofolder.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/html/umbphotofolder.directive.js index deca5dc924..9f1518af29 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/html/umbphotofolder.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/html/umbphotofolder.directive.js @@ -6,124 +6,171 @@ 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) { + img.style = { width: width, height: height }; + } + + /** 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; + //return to 2 dec places + var rounded = Math.round(scaled * 100) / 100; + //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) { + + var currRowWidth = 0; + + var idealImages = imgs.slice(0, idealImgPerRow); + + var maxScaleableHeight = getMaxScaleableHeight(idealImages, maxRowHeight); + + var biggerHeight = Math.max(maxScaleableHeight, minDisplayHeight); + + for (var i = 0; i < idealImages.length; i++) { + var scaledWidth = getScaledWidth( + idealImages[i], + biggerHeight, + maxRowWidth); + currRowWidth += scaledWidth; + } + + if (currRowWidth > maxRowWidth) { + //get the ratio + var r = maxRowWidth / currRowWidth; + var newHeight = biggerHeight * 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 biggerHeight; + } + 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); + } + } + } + + /** builds an image grid row */ + function buildRow(imgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow) { + var currRowWidth = 0; + var row = []; + + var calcRowHeight = getRowHeight(imgs, maxRowHeight, minDisplayHeight, maxRowWidth, idealImgPerRow); + + for (var i = 0; i < imgs.length; i++) { + + var scaledWidth = getScaledWidth(imgs[i], calcRowHeight, maxRowWidth); + if (currRowWidth + scaledWidth <= maxRowWidth) { + currRowWidth += scaledWidth; + setImageStyle(imgs[i], scaledWidth, calcRowHeight); + row.push(imgs[i]); + } + else { + //the max width has been reached + break; + } + } + 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) { 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); + if (row.length > 0) { + rows.push(row); + imagesProcessed += row.length; + } + else { + //if there was nothing processed, exit + break; } } @@ -144,28 +191,35 @@ 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'); + //todo: this doesn't do anything + var border = 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); + 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 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 44453e5c00..e7db6eb4e6 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -243,6 +243,13 @@ ul.color-picker li a { position: relative; } +.umb-photo-folder .picrow div.pic:first-child { + margin-left:0px !important; +} +.umb-photo-folder .picrow div.pic:last-child { + margin-right:0px !important; +} + .umb-photo-folder .picrow div a { display: inline-block; } @@ -258,7 +265,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; diff --git a/src/Umbraco.Web.UI.Client/src/views/directives/html/umb-photo-folder.html b/src/Umbraco.Web.UI.Client/src/views/directives/html/umb-photo-folder.html index fee870e466..708e78d9ed 100644 --- a/src/Umbraco.Web.UI.Client/src/views/directives/html/umb-photo-folder.html +++ b/src/Umbraco.Web.UI.Client/src/views/directives/html/umb-photo-folder.html @@ -1,9 +1,9 @@
-
+
+ style="margin: 0px; padding: 0px; border: none; display: inline-block; overflow: hidden" + ng-style="img.style" ng-repeat="img in row"> - Select - -
-
- + + Select + +
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/folderbrowser/folderbrowser.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/folderbrowser/folderbrowser.html index 54893f4a69..ef203209a2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/folderbrowser/folderbrowser.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/folderbrowser/folderbrowser.html @@ -20,7 +20,7 @@ data-ng-class="{'fileupload-processing': processing() || loadingFiles}"> diff --git a/src/Umbraco.Web.UI/config/ClientDependency.config b/src/Umbraco.Web.UI/config/ClientDependency.config index 8ff4de9cfd..089dec0ec8 100644 --- a/src/Umbraco.Web.UI/config/ClientDependency.config +++ b/src/Umbraco.Web.UI/config/ClientDependency.config @@ -10,7 +10,7 @@ NOTES: * Compression/Combination/Minification is not enabled unless debug="false" is specified on the 'compiliation' element in the web.config * A new version will invalidate both client and server cache and create new persisted files --> - +