/** * Nextcloud - Gallery * * * This file is licensed under the Affero General Public License version 3 or * later. See the COPYING file. * * @author Olivier Paroz * * @copyright Olivier Paroz 2017 */ /* global Handlebars, Gallery, Thumbnails, GalleryImage */ (function ($, Gallery) { "use strict"; /** * Creates a new album object to store information about an album * * @param {string} path * @param {Array} subAlbums * @param {Array} images * @param {string} name * @param {number} fileId * @param {number} mTime * @param {string} etag * @param {number} size * @param {Boolean} sharedWithUser * @param {string} owner * @param {number} freeSpace * @param {number} permissions * @constructor */ var Album = function (path, subAlbums, images, name, fileId, mTime, etag, size, sharedWithUser, owner, freeSpace, permissions) { this.path = path; this.subAlbums = subAlbums; this.images = images; this.viewedItems = 0; this.name = name; this.fileId = fileId; this.mTime = mTime; this.etag = etag; this.size = size; this.sharedWithUser = sharedWithUser; this.owner = owner; this.freeSpace = freeSpace; this.permissions = permissions; this.domDef = null; this.loader = null; this.preloadOffset = 0; }; Album.prototype = { requestId: null, droppableOptions: { accept: '#gallery > .row > a', activeClass: 'album-droppable', hoverClass: 'album-droppable-hover', tolerance: 'pointer' }, /** * Processes UI elements dropped on the album * * @param event * @param ui */ onDrop: function (event, ui) { var $item = ui.draggable; var $clone = ui.helper; var $target = $(event.target); var targetPath = $target.data('dir').toString(); var filePath = $item.data('path').toString(); var fileName = OC.basename(filePath); this.loader.show(); $clone.fadeOut("normal", function () { Gallery.move($item, fileName, filePath, $target, targetPath); }); }, /** * Returns a new album row * * @param {number} width * * @returns {Gallery.Row} */ getRow: function (width) { return new Gallery.Row(width); }, /** * Creates the DOM element for the album and return it immediately so as to not block the * rendering of the rest of the interface * * * Each album also contains a link to open that folder * * An album has a natural size of 200x200 and is comprised of 4 thumbnails which have a * natural size of 200x200 * * Thumbnails are checked first in order to make sure that we have something to show * * @param {number} targetHeight Each row has a specific height * * @return {$} The album to be placed on the row */ getDom: function (targetHeight) { if (this.domDef === null) { var albumElement = Gallery.Templates.galleryalbum({ targetHeight: targetHeight, targetWidth: targetHeight, dir: this.path, path: this.path, permissions: this.permissions, freeSpace: this.freeSpace, label: this.name, targetPath: '#' + encodeURIComponent(this.path) }); this.domDef = $(albumElement); this.loader = this.domDef.children('.album-loader'); this.loader.hide(); this.domDef.click(this._openAlbum.bind(this)); this.droppableOptions.drop = this.onDrop.bind(this); this.domDef.droppable(this.droppableOptions); // Define a if you don't want to set the style in the template //a.width(targetHeight); //a.height(targetHeight); this._fillSubAlbum(targetHeight); } else { this.loader.hide(); } return this.domDef; }, /** * Fills the row with albums and images * * @param {Gallery.Row} row The row to append elements to * * @returns {$.Deferred} */ fillNextRow: function (row) { var def = new $.Deferred(); var numberOfThumbnailsToPreload = 6; var buffer = 5; /** * Add images to the row until it's full * * @todo The number of images to preload should be a user setting * * @param {Album} album * @param {Row} row * @param {Array} images * * @returns {$.Deferred} */ var addRowElements = function (album, row, images) { if ((album.viewedItems + buffer) > album.preloadOffset && (album.preloadOffset < images.length)) { album._preload(numberOfThumbnailsToPreload); } var image = images[album.viewedItems]; return row.addElement(image).then(function (more) { album.viewedItems++; if (more && album.viewedItems < images.length) { return addRowElements(album, row, images); } row.fit(); def.resolve(row); }); }; var items = this.subAlbums.concat(this.images); addRowElements(this, row, items); return def.promise(); }, /** * Returns IDs of thumbnails belonging to the album * * @param {number} count * * @return number[] */ getThumbnailIds: function (count) { var ids = []; var items = this.images.concat(this.subAlbums); for (var i = 0; i < items.length && i < count; i++) { ids = ids.concat(items[i].getThumbnailIds(count)); } return ids; }, /** * Call when the album is clicked on. * * @param event * @private */ _openAlbum: function (event) { event.stopPropagation(); // show loading animation this.loader.show(); if(!_.isUndefined(Gallery.Share)){ Gallery.Share.hideDropDown(); } }, /** * Retrieves a thumbnail and adds it to the album representation * * Only attaches valid thumbnails to the album * * @param {GalleryImage} image * @param {number} targetHeight Each row has a specific height * @param {number} calcWidth Album width * @param {jQuery} imageHolder * * @returns {$.Deferred} * @private */ _getOneImage: function (image, targetHeight, calcWidth, imageHolder) { var backgroundHeight, backgroundWidth; backgroundHeight = (targetHeight / 2); backgroundWidth = calcWidth - 2.01; // Adjust the size because of the margins around pictures backgroundHeight -= 2; imageHolder.css("height", backgroundHeight) .css("width", backgroundWidth); var spinner = $('
'); imageHolder.append(spinner); // img is a Thumbnail.image, true means square thumbnails return image.getThumbnail(true).then(function (img) { if (image.thumbnail.valid) { img.alt = ''; spinner.remove(); imageHolder.css("background-image", "url('" + img.src + "')") .css('opacity', 1); } }); }, /** * Builds the album representation by placing 1 to 4 images on a grid * * @param {Array} images * @param {number} targetHeight Each row has a specific height * @param {object} a * * @returns {$.Deferred} * @private */ _getFourImages: function (images, targetHeight, a) { var calcWidth = targetHeight; var targetWidth; var imagesCount = images.length; var def = new $.Deferred(); var validImages = []; var fail = false; var thumbsArray = []; for (var i = 0; i < imagesCount; i++) { targetWidth = calcWidth; // One picture filling the album if (imagesCount === 1) { targetHeight = 2 * targetHeight; } // 2 bottom pictures out of 3, or 4 pictures have the size of a quarter of the album if ((imagesCount === 3 && i !== 0) || imagesCount === 4) { targetWidth = calcWidth / 2; } // Append the div first in order to not lose the order of images var imageHolder = $('
'); a.append(imageHolder); thumbsArray.push( this._getOneImage(images[i], targetHeight, targetWidth, imageHolder)); } // This technique allows us to wait for all objects to be resolved before making a // decision $.when.apply($, thumbsArray).done(function () { for (var i = 0; i < imagesCount; i++) { // Collect all valid images, just in case if (images[i].thumbnail.valid) { validImages.push(images[i]); } else { fail = true; } } // At least one thumbnail could not be retrieved if (fail) { // Clean up the album a.children().remove(); // Send back the list of images which have thumbnails def.reject(validImages); } }); return def.promise(); }, /** * Fills the album representation with images we've received * * * Each album includes between 1 and 4 images * * Each album is also a link to open that folder * * An album has a natural size of 200x200 and is comprised of 4 thumbnails which have a * natural size of 200x200 The whole thing gets resized to match the targetHeight * * @param {number} targetHeight * @private */ _fillSubAlbum: function (targetHeight) { var album = this; var subAlbum = this.domDef.children('.album'); if (this.images.length >= 1) { this._getFourImages(this.images, targetHeight, subAlbum).fail( function (validImages) { album.images = validImages; album._fillSubAlbum(targetHeight, subAlbum); }); } else { var imageHolder = $('
'); subAlbum.append(imageHolder); this._showFolder(targetHeight, imageHolder); } }, /** * Shows a folder icon in the album since we couldn't get any proper thumbnail * * @param {number} targetHeight * @param imageHolder * @private */ _showFolder: function (targetHeight, imageHolder) { var image = new GalleryImage('Generic folder', 'Generic folder', -1, 'image/svg+xml', null, null); var thumb = Thumbnails.getStandardIcon(-1); image.thumbnail = thumb; this.images.push(image); thumb.loadingDeferred.done(function (img) { img.height = (targetHeight - 2); img.width = (targetHeight) - 2; imageHolder.append(img); imageHolder.css('opacity', 1); }); }, /** * Preloads the first $count thumbnails * * @param {number} count * @private */ _preload: function (count) { var items = this.subAlbums.concat(this.images); var realCounter = 0; var maxThumbs = 0; var fileIds = []; var squareFileIds = []; for (var i = this.preloadOffset; i < this.preloadOffset + count && i < items.length; i++) { if (items[i].subAlbums) { maxThumbs = 4; var imagesLength = items[i].images.length; if (imagesLength > 0 && imagesLength < 4) { maxThumbs = imagesLength; } var squareFileId = items[i].getThumbnailIds(maxThumbs); squareFileIds = squareFileIds.concat(squareFileId); realCounter = realCounter + maxThumbs; } else { var fileId = items[i].getThumbnailIds(); fileIds = fileIds.concat(fileId); realCounter++; } if (realCounter >= count) { i++; break; } } this.preloadOffset = i; Thumbnails.loadBatch(fileIds, false); Thumbnails.loadBatch(squareFileIds, true); } }; window.Album = Album; })(jQuery, Gallery);