Issues (263)

js/galleryalbum.js (1 issue)

1
/**
2
 * Nextcloud - Gallery
3
 *
4
 *
5
 * This file is licensed under the Affero General Public License version 3 or
6
 * later. See the COPYING file.
7
 *
8
 * @author Olivier Paroz <[email protected]>
9
 *
10
 * @copyright Olivier Paroz 2017
11
 */
12
/* global Handlebars, Gallery, Thumbnails, GalleryImage */
13
(function ($, Gallery) {
14
	"use strict";
15
16
	/**
17
	 * Creates a new album object to store information about an album
18
	 *
19
	 * @param {string} path
20
	 * @param {Array<Album|GalleryImage>} subAlbums
21
	 * @param {Array<Album|GalleryImage>} images
22
	 * @param {string} name
23
	 * @param {number} fileId
24
	 * @param {number} mTime
25
	 * @param {string} etag
26
	 * @param {number} size
27
	 * @param {Boolean} sharedWithUser
28
	 * @param {string} owner
29
	 * @param {number} freeSpace
30
	 * @param {number} permissions
31
	 * @constructor
32
	 */
33
	var Album = function (path, subAlbums, images, name, fileId, mTime, etag, size, sharedWithUser,
34
						  owner, freeSpace, permissions) {
35
		this.path = path;
36
		this.subAlbums = subAlbums;
37
		this.images = images;
38
		this.viewedItems = 0;
39
		this.name = name;
40
		this.fileId = fileId;
41
		this.mTime = mTime;
42
		this.etag = etag;
43
		this.size = size;
44
		this.sharedWithUser = sharedWithUser;
45
		this.owner = owner;
46
		this.freeSpace = freeSpace;
47
		this.permissions = permissions;
48
		this.domDef = null;
49
		this.loader = null;
50
		this.preloadOffset = 0;
51
	};
52
53
	Album.prototype = {
54
		requestId: null,
55
		droppableOptions: {
56
			accept: '#gallery > .row > a',
57
			activeClass: 'album-droppable',
58
			hoverClass: 'album-droppable-hover',
59
			tolerance: 'pointer'
60
		},
61
62
		/**
63
		 * Processes UI elements dropped on the album
64
		 *
65
		 * @param event
66
		 * @param ui
67
		 */
68
		onDrop: function (event, ui) {
69
			var $item = ui.draggable;
70
			var $clone = ui.helper;
71
			var $target = $(event.target);
72
			var targetPath = $target.data('dir').toString();
73
			var filePath = $item.data('path').toString();
74
			var fileName = OC.basename(filePath);
75
76
			this.loader.show();
77
78
			$clone.fadeOut("normal", function () {
79
				Gallery.move($item, fileName, filePath, $target, targetPath);
80
			});
81
		},
82
83
		/**
84
		 * Returns a new album row
85
		 *
86
		 * @param {number} width
87
		 *
88
		 * @returns {Gallery.Row}
89
		 */
90
		getRow: function (width) {
91
			return new Gallery.Row(width);
92
		},
93
94
		/**
95
		 * Creates the DOM element for the album and return it immediately so as to not block the
96
		 * rendering of the rest of the interface
97
		 *
98
		 *    * Each album also contains a link to open that folder
99
		 *    * An album has a natural size of 200x200 and is comprised of 4 thumbnails which have a
100
		 *        natural size of 200x200
101
		 *    * Thumbnails are checked first in order to make sure that we have something to show
102
		 *
103
		 * @param {number} targetHeight Each row has a specific height
104
		 *
105
		 * @return {$} The album to be placed on the row
106
		 */
107
		getDom: function (targetHeight) {
108
			if (this.domDef === null) {
109
				var albumElement = Gallery.Templates.galleryalbum({
110
					targetHeight: targetHeight,
111
					targetWidth: targetHeight,
112
					dir: this.path,
113
					path: this.path,
114
					permissions: this.permissions,
115
					freeSpace: this.freeSpace,
116
					label: this.name,
117
					targetPath: '#' + encodeURIComponent(this.path)
118
				});
119
				this.domDef = $(albumElement);
120
				this.loader = this.domDef.children('.album-loader');
121
				this.loader.hide();
122
				this.domDef.click(this._openAlbum.bind(this));
123
124
				this.droppableOptions.drop = this.onDrop.bind(this);
125
				this.domDef.droppable(this.droppableOptions);
126
127
				// Define a if you don't want to set the style in the template
128
				//a.width(targetHeight);
129
				//a.height(targetHeight);
130
131
				this._fillSubAlbum(targetHeight);
132
			} else {
133
				this.loader.hide();
134
			}
135
136
			return this.domDef;
137
		},
138
139
		/**
140
		 * Fills the row with albums and images
141
		 *
142
		 * @param {Gallery.Row} row The row to append elements to
143
		 *
144
		 * @returns {$.Deferred<Gallery.Row>}
145
		 */
146
		fillNextRow: function (row) {
147
			var def = new $.Deferred();
148
			var numberOfThumbnailsToPreload = 6;
149
			var buffer = 5;
150
151
			/**
152
			 * Add images to the row until it's full
153
			 *
154
			 * @todo The number of images to preload should be a user setting
155
			 *
156
			 * @param {Album} album
157
			 * @param {Row} row
158
			 * @param {Array<Album|GalleryImage>} images
159
			 *
160
			 * @returns {$.Deferred<Gallery.Row>}
161
			 */
162
			var addRowElements = function (album, row, images) {
163
				if ((album.viewedItems + buffer) > album.preloadOffset &&
164
					(album.preloadOffset < images.length)) {
165
					album._preload(numberOfThumbnailsToPreload);
166
				}
167
168
				var image = images[album.viewedItems];
169
				return row.addElement(image).then(function (more) {
170
					album.viewedItems++;
171
					if (more && album.viewedItems < images.length) {
172
						return addRowElements(album, row, images);
173
					}
174
					row.fit();
175
					def.resolve(row);
176
				});
177
			};
178
			var items = this.subAlbums.concat(this.images);
179
			addRowElements(this, row, items);
180
			return def.promise();
181
		},
182
183
		/**
184
		 * Returns IDs of thumbnails belonging to the album
185
		 *
186
		 * @param {number} count
187
		 *
188
		 * @return number[]
189
		 */
190
		getThumbnailIds: function (count) {
191
			var ids = [];
192
			var items = this.images.concat(this.subAlbums);
193
			for (var i = 0; i < items.length && i < count; i++) {
194
				ids = ids.concat(items[i].getThumbnailIds(count));
195
			}
196
197
			return ids;
198
		},
199
200
		/**
201
		 * Call when the album is clicked on.
202
		 *
203
		 * @param event
204
		 * @private
205
		 */
206
		_openAlbum: function (event) {
207
			event.stopPropagation();
208
			// show loading animation
209
			this.loader.show();
210
			if(!_.isUndefined(Gallery.Share)){
211
				Gallery.Share.hideDropDown();
212
			}
213
		},
214
215
		/**
216
		 * Retrieves a thumbnail and adds it to the album representation
217
		 *
218
		 * Only attaches valid thumbnails to the album
219
		 *
220
		 * @param {GalleryImage} image
221
		 * @param {number} targetHeight Each row has a specific height
222
		 * @param {number} calcWidth Album width
223
		 * @param {jQuery} imageHolder
224
		 *
225
		 * @returns {$.Deferred<Thumbnail>}
226
		 * @private
227
		 */
228
		_getOneImage: function (image, targetHeight, calcWidth, imageHolder) {
229
			var backgroundHeight, backgroundWidth;
230
231
			backgroundHeight = (targetHeight / 2);
232
			backgroundWidth = calcWidth - 2.01;
233
234
			// Adjust the size because of the margins around pictures
235
			backgroundHeight -= 2;
236
237
			imageHolder.css("height", backgroundHeight)
238
				.css("width", backgroundWidth);
239
			var spinner = $('<div class="icon-loading">');
240
			imageHolder.append(spinner);
241
242
			// img is a Thumbnail.image, true means square thumbnails
243
			return image.getThumbnail(true).then(function (img) {
244
				if (image.thumbnail.valid) {
245
					img.alt = '';
246
					spinner.remove();
247
					imageHolder.css("background-image", "url('" + img.src + "')")
248
						.css('opacity', 1);
249
				}
250
			});
251
		},
252
253
		/**
254
		 * Builds the album representation by placing 1 to 4 images on a grid
255
		 *
256
		 * @param {Array<GalleryImage>} images
257
		 * @param {number} targetHeight Each row has a specific height
258
		 * @param {object} a
259
		 *
260
		 * @returns {$.Deferred<Array>}
261
		 * @private
262
		 */
263
		_getFourImages: function (images, targetHeight, a) {
264
			var calcWidth = targetHeight;
265
			var targetWidth;
266
			var imagesCount = images.length;
267
			var def = new $.Deferred();
268
			var validImages = [];
269
			var fail = false;
270
			var thumbsArray = [];
271
272
			for (var i = 0; i < imagesCount; i++) {
273
				targetWidth = calcWidth;
274
				// One picture filling the album
275
				if (imagesCount === 1) {
276
					targetHeight = 2 * targetHeight;
277
				}
278
				// 2 bottom pictures out of 3, or 4 pictures have the size of a quarter of the album
279
				if ((imagesCount === 3 && i !== 0) || imagesCount === 4) {
280
					targetWidth = calcWidth / 2;
281
				}
282
283
				// Append the div first in order to not lose the order of images
284
				var imageHolder = $('<div class="cropped">');
285
				a.append(imageHolder);
286
				thumbsArray.push(
287
					this._getOneImage(images[i], targetHeight, targetWidth, imageHolder));
288
			}
289
290
			// This technique allows us to wait for all objects to be resolved before making a
291
			// decision
292
			$.when.apply($, thumbsArray).done(function () {
293
				for (var i = 0; i < imagesCount; i++) {
294
					// Collect all valid images, just in case
295
					if (images[i].thumbnail.valid) {
296
						validImages.push(images[i]);
297
					} else {
298
						fail = true;
299
					}
300
				}
301
302
				// At least one thumbnail could not be retrieved
303
				if (fail) {
304
					// Clean up the album
305
					a.children().remove();
306
					// Send back the list of images which have thumbnails
307
					def.reject(validImages);
308
				}
309
			});
310
311
			return def.promise();
312
		},
313
314
		/**
315
		 * Fills the album representation with images we've received
316
		 *
317
		 *    * Each album includes between 1 and 4 images
318
		 *    * Each album is also a link to open that folder
319
		 *    * An album has a natural size of 200x200 and is comprised of 4 thumbnails which have a
320
		 * natural size of 200x200 The whole thing gets resized to match the targetHeight
321
		 *
322
		 * @param {number} targetHeight
323
		 * @private
324
		 */
325
		_fillSubAlbum: function (targetHeight) {
326
			var album = this;
327
			var subAlbum = this.domDef.children('.album');
328
329
			if (this.images.length >= 1) {
330
				this._getFourImages(this.images, targetHeight, subAlbum).fail(
331
					function (validImages) {
332
						album.images = validImages;
333
						album._fillSubAlbum(targetHeight, subAlbum);
334
					});
335
			} else {
336
				var imageHolder = $('<div class="cropped">');
337
				subAlbum.append(imageHolder);
338
				this._showFolder(targetHeight, imageHolder);
339
			}
340
		},
341
342
		/**
343
		 * Shows a folder icon in the album since we couldn't get any proper thumbnail
344
		 *
345
		 * @param {number} targetHeight
346
		 * @param imageHolder
347
		 * @private
348
		 */
349
		_showFolder: function (targetHeight, imageHolder) {
350
			var image = new GalleryImage('Generic folder', 'Generic folder', -1, 'image/svg+xml',
351
				null, null);
352
			var thumb = Thumbnails.getStandardIcon(-1);
353
			image.thumbnail = thumb;
354
			this.images.push(image);
355
			thumb.loadingDeferred.done(function (img) {
356
				img.height = (targetHeight - 2);
357
				img.width = (targetHeight) - 2;
358
				imageHolder.append(img);
359
				imageHolder.css('opacity', 1);
360
			});
361
		},
362
363
		/**
364
		 * Preloads the first $count thumbnails
365
		 *
366
		 * @param {number} count
367
		 * @private
368
		 */
369
		_preload: function (count) {
370
			var items = this.subAlbums.concat(this.images);
371
			var realCounter = 0;
372
			var maxThumbs = 0;
373
			var fileIds = [];
374
			var squareFileIds = [];
375
			for (var i = this.preloadOffset; i < this.preloadOffset + count &&
376
			i < items.length; i++) {
377
				if (items[i].subAlbums) {
378
					maxThumbs = 4;
379
					var imagesLength = items[i].images.length;
380
					if (imagesLength > 0 && imagesLength < 4) {
381
						maxThumbs = imagesLength;
382
					}
383
					var squareFileId = items[i].getThumbnailIds(maxThumbs);
384
					squareFileIds = squareFileIds.concat(squareFileId);
385
					realCounter = realCounter + maxThumbs;
386
				} else {
387
					var fileId = items[i].getThumbnailIds();
388
					fileIds = fileIds.concat(fileId);
389
					realCounter++;
390
				}
391
				if (realCounter >= count) {
392
					i++;
0 ignored issues
show
Complexity Coding Style introduced by
You seem to be assigning a new value to the loop variable i here. Please check if this was indeed your intention. Even if it was, consider using another kind of loop instead.
Loading history...
393
					break;
394
				}
395
			}
396
397
			this.preloadOffset = i;
398
			Thumbnails.loadBatch(fileIds, false);
399
			Thumbnails.loadBatch(squareFileIds, true);
400
		}
401
	};
402
403
	window.Album = Album;
404
})(jQuery, Gallery);
405