Issues (263)

js/gallery.js (2 issues)

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 Album, GalleryImage */
13
(function ($, OC, t) {
14
	"use strict";
15
	var Gallery = {
16
		currentAlbum: null,
17
		currentEtag: null,
18
		config: {},
19
		/** Map of the whole gallery, built as we navigate through folders */
20
		albumMap: {},
21
		/** Used to pick an image based on the URL */
22
		imageMap: {},
23
		appName: 'gallery',
24
		token: undefined,
25
		activeSlideShow: null,
26
		buttonsWidth: 600,
27
		browserToolbarHeight: 150,
28
		filesClient: null,
29
30
		/**
31
		 * Refreshes the view and starts the slideshow if required
32
		 *
33
		 * @param {string} path
34
		 * @param {string} albumPath
35
		 */
36
		refresh: function (path, albumPath) {
37
			if (Gallery.currentAlbum !== albumPath) {
38
				Gallery.view.init(albumPath, null);
39
			}
40
41
			// If the path is mapped, that means that it's an albumPath
42
			if (Gallery.albumMap[path]) {
43
				if (Gallery.activeSlideShow) {
44
					Gallery.activeSlideShow.stop();
45
				}
46
			} else if (Gallery.imageMap[path] && Gallery.activeSlideShow.active === false) {
47
				Gallery.view.startSlideshow(path, albumPath);
48
			}
49
		},
50
51
		/**
52
		 * Retrieves information about all the images and albums located in the current folder
53
		 *
54
		 * @param {string} currentLocation
55
		 *
56
		 * @returns {*}
57
		 */
58
		getFiles: function (currentLocation) {
59
			// Cache the sorting order of the current album before loading new files
60
			if (!$.isEmptyObject(Gallery.albumMap)) {
61
				Gallery.albumMap[Gallery.currentAlbum].sorting = Gallery.config.albumSorting;
62
			}
63
			// Checks if we've visited this location before ands saves the etag to use for
64
			// comparison later
65
			var albumEtag;
66
			var albumCache = Gallery.albumMap[decodeURIComponent(currentLocation)];
67
			if (!$.isEmptyObject(albumCache)) {
68
				albumEtag = albumCache.etag;
69
			}
70
71
			// Sends the request to the server
72
			var params = {
73
				location: currentLocation,
74
				mediatypes: Gallery.config.getMediaTypes(),
75
				features: Gallery.config.getFeatures(),
76
				etag: albumEtag
0 ignored issues
show
The variable albumEtag does not seem to be initialized in case !$.isEmptyObject(albumCache) on line 67 is false. Are you sure this can never be the case?
Loading history...
77
			};
78
			// Only use the folder as a GET parameter and not as part of the URL
79
			var url = Gallery.utility.buildGalleryUrl('files', '/list', params);
80
			return $.getJSON(url).then(
81
				function (/**@type{{
82
					* files:Array,
83
					* albums:Array,
84
					* albumconfig:Object,
85
					* albumpath:String,
86
					* updated:Boolean}}*/
87
						  data) {
88
					var albumpath = data.albumpath;
89
					var updated = data.updated;
90
					// FIXME albumConfig should be cached as well
91
					/**@type {{design,information,sorting,error: string}}*/
92
					var albumConfig = data.albumconfig;
93
					//Gallery.config.setAlbumPermissions(currentAlbum);
94
					Gallery.config.setAlbumConfig(albumConfig, albumpath);
95
					// Both the folder and the etag have to match
96
					if ((decodeURIComponent(currentLocation) === albumpath) &&
97
						(updated === false)) {
98
						Gallery.imageMap = albumCache.imageMap;
99
					} else {
100
						Gallery._mapFiles(data);
101
					}
102
103
					// Restore the previous sorting order for this album
104
					if (!$.isEmptyObject(Gallery.albumMap[albumpath].sorting)) {
105
						Gallery.config.updateAlbumSorting(
106
							Gallery.albumMap[albumpath].sorting);
107
					}
108
109
				}, function (xhr) {
110
					var result = xhr.responseJSON;
111
					var albumPath = decodeURIComponent(currentLocation);
112
					var message;
113
					if (result === null) {
114
						message = t('gallery', 'There was a problem reading files from this album');
115
					} else {
116
						message = result.message;
117
					}
118
					Gallery.view.init(albumPath, message);
119
					Gallery._mapStructure(albumPath);
120
				});
121
		},
122
123
		/**
124
		 * Sorts albums and images based on user preferences
125
		 */
126
		sorter: function () {
127
			var sortType = 'name';
128
			var sortOrder = 'asc';
129
			var albumSortType = 'name';
130
			var albumSortOrder = 'asc';
131
			if (this.id === 'sort-date-button') {
132
				sortType = 'date';
133
134
			}
135
			var currentSort = Gallery.config.albumSorting;
136
			if (currentSort.type === sortType && currentSort.order === sortOrder) {
137
				sortOrder = 'des';
138
			}
139
140
			// Update the controls
141
			Gallery.view.sortControlsSetup(sortType, sortOrder);
142
143
			// We can't currently sort by album creation time
144
			if (sortType === 'name') {
145
				albumSortOrder = sortOrder;
146
			}
147
148
			// FIXME Rendering is still happening while we're sorting...
149
150
			// Clear before sorting
151
			Gallery.view.clear();
152
153
			// Sort the images
154
			Gallery.albumMap[Gallery.currentAlbum].images.sort(Gallery.utility.sortBy(sortType,
155
				sortOrder));
156
			Gallery.albumMap[Gallery.currentAlbum].subAlbums.sort(
157
				Gallery.utility.sortBy(albumSortType,
158
					albumSortOrder));
159
160
			// Save the new settings
161
			var sortConfig = {
162
				type: sortType,
163
				order: sortOrder,
164
				albumOrder: albumSortOrder
165
			};
166
			Gallery.config.updateAlbumSorting(sortConfig);
167
168
			// Refresh the view
169
			Gallery.view.viewAlbum(Gallery.currentAlbum);
170
		},
171
172
		/**
173
		 * Switches to the Files view
174
		 *
175
		 * @param event
176
		 */
177
		switchToFilesView: function (event) {
178
			event.stopPropagation();
179
180
			var subUrl = '';
181
			var params = {path: '/' + Gallery.currentAlbum};
182
			if (Gallery.token) {
183
				params.token = Gallery.token;
184
				subUrl = 's/{token}?path={path}';
185
			} else {
186
				subUrl = 'apps/files?dir={path}';
187
			}
188
189
			var button = $('#filelist-button');
190
			button.children('img').addClass('hidden');
191
			button.children('#button-loading').removeClass('hidden').addClass('icon-loading-small');
192
			OC.redirect(OC.generateUrl(subUrl, params));
193
		},
194
195
		/**
196
		 * Populates the share dialog with the needed information
197
		 *
198
		 * @param event
199
		 */
200
		share: function (event) {
201
			// Clicking on share button does not trigger automatic slide-up
202
			$('.album-info-container').slideUp();
203
204
			if (!Gallery.Share.droppedDown) {
205
				event.preventDefault();
206
				event.stopPropagation();
207
208
				var currentAlbum = Gallery.albumMap[Gallery.currentAlbum];
209
				$('#controls a.share').data('path', currentAlbum.path)
210
					.data('link', true)
211
					.data('item-source', currentAlbum.fileId)
212
					.data('possible-permissions', currentAlbum.permissions)
213
					.click();
214
			}
215
		},
216
217
		/**
218
		 * Sends an archive of the current folder to the browser
219
		 *
220
		 * @param event
221
		 */
222
		download: function (event) {
223
			event.preventDefault();
224
225
			var path = $('#app-content').data('albumname');
226
			var files = Gallery.currentAlbum;
227
			var downloadUrl = Gallery.utility.buildFilesUrl(path, files);
228
229
			OC.redirect(downloadUrl);
230
		},
231
232
		/**
233
		 * Shows an information box to the user
234
		 *
235
		 * @param event
236
		 */
237
		showInfo: function (event) {
238
			event.stopPropagation();
239
			Gallery.infoBox.showInfo();
240
		},
241
242
		/**
243
		 * Sends the shared files to the viewer's cloud
244
		 *
245
		 * @param event
246
		 */
247
		saveForm: function (event) {
248
			event.preventDefault();
249
250
			var saveElement = $('#save-external-share');
251
			var remote = $(this).find('input[type="text"]').val();
252
			var owner = saveElement.data('owner');
253
			var name = saveElement.data('name');
254
			var isProtected = saveElement.data('protected');
255
			Gallery._saveToServer(remote, Gallery.token, owner, name, isProtected);
256
		},
257
258
		/**
259
		 * Creates a new slideshow using the images found in the current folder
260
		 *
261
		 * @param {Array} images
262
		 * @param {string} startImage
263
		 * @param {boolean} autoPlay
264
		 *
265
		 * @returns {boolean}
266
		 */
267
		slideShow: function (images, startImage, autoPlay) {
268
			if (startImage === undefined) {
269
				OC.Notification.showTemporary(t('gallery',
270
					'Aborting preview. Could not find the file'));
271
				return false;
272
			}
273
			var start = images.indexOf(startImage);
274
			images = images.filter(function (image, index) {
275
				// If the slideshow is loaded before we get a thumbnail, we have to accept all
276
				// images
277
				if (!image.thumbnail) {
278
					return image;
279
				} else {
280
					if (image.thumbnail.valid) {
281
						return image;
282
					} else if (index < images.indexOf(startImage)) {
283
						start--;
284
					}
285
				}
286
			}).map(function (image) {
287
				var name = OC.basename(image.path);
288
				var previewUrl = Gallery.utility.getPreviewUrl(image.fileId, image.etag);
289
				var params = {
290
					c: image.etag,
291
					requesttoken: oc_requesttoken
0 ignored issues
show
The variable oc_requesttoken seems to be never declared. If this is a global, consider adding a /** global: oc_requesttoken */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
292
				};
293
				var downloadUrl = Gallery.utility.buildGalleryUrl('files',
294
					'/download/' + image.fileId,
295
					params);
296
297
				return {
298
					name: name,
299
					path: image.path,
300
					file: image.fileId,
301
					mimeType: image.mimeType,
302
					permissions: image.permissions,
303
					url: previewUrl,
304
					downloadUrl: downloadUrl
305
				};
306
			});
307
			Gallery.activeSlideShow.setImages(images, autoPlay);
308
			Gallery.activeSlideShow.onStop = function () {
309
				$('#content').show();
310
				Gallery.view.removeLoading();
311
				if (Gallery.currentAlbum !== '') {
312
					// Only modern browsers can manipulate history
313
					if (history && history.replaceState) {
314
						history.replaceState('', '',
315
							'#' + encodeURIComponent(Gallery.currentAlbum));
316
					} else {
317
						location.hash = '#' + encodeURIComponent(Gallery.currentAlbum);
318
					}
319
				} else {
320
					// Only modern browsers can manipulate history
321
					if (history && history.replaceState) {
322
						history.replaceState('', '', '#');
323
					} else {
324
						location.hash = '#';
325
					}
326
				}
327
			};
328
			Gallery.activeSlideShow.show(start);
329
			if(!_.isUndefined(Gallery.Share)){
330
				Gallery.Share.hideDropDown();
331
			}
332
			$('.album-info-container').slideUp();
333
			// Resets the last focused element
334
			document.activeElement.blur();
335
		},
336
337
		/**
338
		 * Moves files and albums to a new location
339
		 *
340
		 * @param {jQuery} $item
341
		 * @param {string} fileName
342
		 * @param {string} filePath
343
		 * @param {jQuery} $target
344
		 * @param {string} targetPath
345
		 */
346
		move: function ($item, fileName, filePath, $target, targetPath) {
347
			var self = this;
348
			var dir = Gallery.currentAlbum;
349
350
			if (targetPath.charAt(targetPath.length - 1) !== '/') {
351
				// make sure we move the files into the target dir,
352
				// not overwrite it
353
				targetPath = targetPath + '/';
354
			}
355
			self.filesClient.move(dir + '/' + fileName, targetPath + fileName)
356
				.done(function () {
357
					self._removeElement(dir, filePath, $item);
358
				})
359
				.fail(function (status) {
360
					if (status === 412) {
361
						// TODO: some day here we should invoke the conflict dialog
362
						OC.Notification.showTemporary(
363
							t('gallery', 'Could not move "{file}", target exists', {file: fileName})
364
						);
365
					} else {
366
						OC.Notification.showTemporary(
367
							t('gallery', 'Could not move "{file}"', {file: fileName})
368
						);
369
					}
370
					$item.fadeTo("normal", 1);
371
					$target.children('.album-loader').hide();
372
				})
373
				.always(function () {
374
					// Nothing?
375
				});
376
		},
377
378
		/**
379
		 * Builds the album's model
380
		 *
381
		 * @param {{
382
		 * 	files:Array,
383
		 * 	albums:Array,
384
		 * 	albumconfig:Object,
385
		 * 	albumpath:String,
386
		 *	updated:Boolean
387
		 * 	}} data
388
		 * @private
389
		 */
390
		_mapFiles: function (data) {
391
			Gallery.imageMap = {};
392
			var image = null;
393
			var path = null;
394
			var fileId = null;
395
			var mimeType = null;
396
			var mTime = null;
397
			var etag = null;
398
			var size = null;
399
			var sharedWithUser = null;
400
			var owner = null;
401
			var permissions = 0;
402
			var currentLocation = data.albumpath;
403
			// This adds a new node to the map for each parent album
404
			Gallery._mapStructure(currentLocation);
405
			var files = data.files;
406
			if (files.length > 0) {
407
				var subAlbumCache = {};
408
				var albumCache = Gallery.albumMap[currentLocation]
409
					= new Album(
410
					currentLocation,
411
					[],
412
					[],
413
					OC.basename(currentLocation),
414
					data.albums[currentLocation].nodeid,
415
					data.albums[currentLocation].mtime,
416
					data.albums[currentLocation].etag,
417
					data.albums[currentLocation].size,
418
					data.albums[currentLocation].sharedwithuser,
419
					data.albums[currentLocation].owner,
420
					data.albums[currentLocation].freespace,
421
					data.albums[currentLocation].permissions
422
				);
423
				for (var i = 0; i < files.length; i++) {
424
					path = files[i].path;
425
					fileId = files[i].nodeid;
426
					mimeType = files[i].mimetype;
427
					mTime = files[i].mtime;
428
					etag = files[i].etag;
429
					size = files[i].size;
430
					sharedWithUser = files[i].sharedwithuser;
431
					owner = files[i].owner;
432
					permissions = files[i].permissions;
433
434
					image =
435
						new GalleryImage(
436
							path, path, fileId, mimeType, mTime, etag, size, sharedWithUser, owner, permissions
437
						);
438
439
					// Determines the folder name for the image
440
					var dir = OC.dirname(path);
441
					if (dir === path) {
442
						dir = '';
443
					}
444
					if (dir === currentLocation) {
445
						// The image belongs to the current album, so we can add it directly
446
						albumCache.images.push(image);
447
					} else {
448
						// The image belongs to a sub-album, so we create a sub-album cache if it
449
						// doesn't exist and add images to it
450
						if (!subAlbumCache[dir]) {
451
							subAlbumCache[dir] = new Album(
452
								dir,
453
								[],
454
								[],
455
								OC.basename(dir),
456
								data.albums[dir].nodeid,
457
								data.albums[dir].mtime,
458
								data.albums[dir].etag,
459
								data.albums[dir].size,
460
								data.albums[dir].sharedwithuser,
461
								data.albums[currentLocation].owner,
462
								data.albums[currentLocation].freespace,
463
								data.albums[dir].permissions);
464
						}
465
						subAlbumCache[dir].images.push(image);
466
						// The sub-album also has to be added to the global map
467
						if (!Gallery.albumMap[dir]) {
468
							Gallery.albumMap[dir] = {};
469
						}
470
					}
471
					Gallery.imageMap[image.path] = image;
472
				}
473
				// Adds the sub-albums to the current album
474
				Gallery._mapAlbums(albumCache, subAlbumCache);
475
476
				// Caches the information which is not already cached
477
				albumCache.etag = data.albums[currentLocation].etag;
478
				albumCache.imageMap = Gallery.imageMap;
479
			}
480
		},
481
482
		/**
483
		 * Adds every album leading to the current folder to a global album map
484
		 *
485
		 * Per example, if you have Root/Folder1/Folder2/CurrentFolder then the map will contain:
486
		 *    * Root
487
		 *    * Folder1
488
		 *    * Folder2
489
		 *    * CurrentFolder
490
		 *
491
		 *  Every time a new location is loaded, the map is completed
492
		 *
493
		 *
494
		 * @param {string} path
495
		 *
496
		 * @returns {Album}
497
		 * @private
498
		 */
499
		_mapStructure: function (path) {
500
			if (!Gallery.albumMap[path]) {
501
				Gallery.albumMap[path] = {};
502
				// Builds relationships between albums
503
				if (path !== '') {
504
					var parent = OC.dirname(path);
505
					if (parent === path) {
506
						parent = '';
507
					}
508
					Gallery._mapStructure(parent);
509
				}
510
			}
511
			return Gallery.albumMap[path];
512
		},
513
514
		/**
515
		 * Adds the sub-albums to the current album
516
		 *
517
		 * @param {Album} albumCache
518
		 * @param {{Album}} subAlbumCache
519
		 * @private
520
		 */
521
		_mapAlbums: function (albumCache, subAlbumCache) {
522
			for (var j = 0, keys = Object.keys(subAlbumCache); j <
523
			keys.length; j++) {
524
				albumCache.subAlbums.push(subAlbumCache[keys[j]]);
525
			}
526
		},
527
528
		/**
529
		 * Saves the folder to a remote server
530
		 *
531
		 * Our location is the remote for the other server
532
		 *
533
		 * @param {string} remote
534
		 * @param {string}token
535
		 * @param {string}owner
536
		 * @param {string}name
537
		 * @param {boolean} isProtected
538
		 * @private
539
		 */
540
		_saveToServer: function (remote, token, owner, name, isProtected) {
541
			var location = window.location.protocol + '//' + window.location.host + OC.webroot;
542
			var isProtectedInt = (isProtected) ? 1 : 0;
543
			var url = remote + '/index.php/apps/files#' + 'remote=' + encodeURIComponent(location)
544
				+ "&token=" + encodeURIComponent(token) + "&owner=" + encodeURIComponent(owner) +
545
				"&name=" +
546
				encodeURIComponent(name) + "&protected=" + isProtectedInt;
547
548
			if (remote.indexOf('://') > 0) {
549
				OC.redirect(url);
550
			} else {
551
				// if no protocol is specified, we automatically detect it by testing https and
552
				// http
553
				// this check needs to happen on the server due to the Content Security Policy
554
				// directive
555
				$.get(OC.generateUrl('apps/files_sharing/testremote'),
556
					{remote: remote}).then(function (protocol) {
557
					if (protocol !== 'http' && protocol !== 'https') {
558
						OC.dialogs.alert(t('files_sharing',
559
							'No compatible server found at {remote}',
560
							{remote: remote}),
561
							t('files_sharing', 'Invalid server url'));
562
					} else {
563
						OC.redirect(protocol + '://' + url);
564
					}
565
				});
566
			}
567
		},
568
569
		/**
570
		 * Removes the moved element from the UI and refreshes the view
571
		 *
572
		 * @param {string} dir
573
		 * @param {string}filePath
574
		 * @param {jQuery} $item
575
		 * @private
576
		 */
577
		_removeElement: function (dir, filePath, $item) {
578
			var images = Gallery.albumMap[Gallery.currentAlbum].images;
579
			var albums = Gallery.albumMap[Gallery.currentAlbum].subAlbums;
580
			// if still viewing the same directory
581
			if (Gallery.currentAlbum === dir) {
582
				var removed = false;
583
				// We try to see if an image was removed
584
				var movedImage = _(images).findIndex({path: filePath});
585
				if (movedImage >= 0) {
586
					images.splice(movedImage, 1);
587
					removed = true;
588
				} else {
589
					// It wasn't an image, so try to remove an album
590
					var movedAlbum = _(albums).findIndex({path: filePath});
591
					if (movedAlbum >= 0) {
592
						albums.splice(movedAlbum, 1);
593
						removed = true;
594
					}
595
				}
596
597
				if (removed) {
598
					$item.remove();
599
					// Refresh the photowall without checking if new files have arrived in the
600
					// current album
601
					// TODO On the next visit this album is going to be reloaded, unless we can get
602
					// an etag back from the move endpoint
603
					Gallery.view.init(Gallery.currentAlbum);
604
				}
605
			}
606
		}
607
	};
608
	window.Gallery = Gallery;
609
})(jQuery, OC, t);
610