Completed
Pull Request — master (#238)
by Olivier
02:39
created

View._setupUploader   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
c 0
b 0
f 0
nc 3
nop 1
dl 0
loc 9
rs 9.6666
1
/* global Handlebars, Gallery, Thumbnails */
2
(function ($, _, OC, t, Gallery) {
3
	"use strict";
4
5
	var TEMPLATE_ADDBUTTON = '<a href="#" class="button new"><span class="icon icon-add"></span><span class="hidden-visually">New</span></a>';
0 ignored issues
show
Coding Style introduced by
Line is too long.
Loading history...
6
7
	/**
8
	 * Builds and updates the Gallery view
9
	 *
10
	 * @constructor
11
	 */
12
	var View = function () {
13
		this.element = $('#gallery');
14
		this.loadVisibleRows.loading = false;
15
		this._setupUploader();
16
		this.breadcrumb = new Gallery.Breadcrumb();
17
		this.emptyContentElement = $('#emptycontent');
18
		this.controlsElement = $('#controls');
19
	};
20
21
	View.prototype = {
22
		element: null,
23
		breadcrumb: null,
24
		requestId: -1,
25
		emptyContentElement: null,
26
		controlsElement: null,
27
28
		/**
29
		 * Removes all thumbnails from the view
30
		 */
31
		clear: function () {
32
			this.loadVisibleRows.processing = false;
33
			this.loadVisibleRows.loading = null;
34
			// We want to keep all the events
35
			this.element.children().detach();
36
			this.showLoading();
37
		},
38
39
		/**
40
		 * @param {string} path
41
		 * @returns {boolean}
42
		 */
43
		_isValidPath: function(path) {
44
			var sections = path.split('/');
45
			for (var i = 0; i < sections.length; i++) {
46
				if (sections[i] === '..') {
47
					return false;
48
				}
49
			}
50
51
			return path.toLowerCase().indexOf(decodeURI('%0a')) === -1 &&
52
				path.toLowerCase().indexOf(decodeURI('%00')) === -1;
53
		},
54
55
		/**
56
		 * Populates the view if there are images or albums to show
57
		 *
58
		 * @param {string} albumPath
59
		 * @param {string|undefined} errorMessage
60
		 */
61
		init: function (albumPath, errorMessage) {
62
			// Set path to an empty value if not a valid one
63
			if(!this._isValidPath(albumPath)) {
64
				albumPath = '';
65
			}
66
67
			// Only do it when the app is initialised
68
			if (this.requestId === -1) {
69
				this._initButtons();
70
				this._blankUrl();
71
			}
72
			if ($.isEmptyObject(Gallery.imageMap)) {
73
				Gallery.view.showEmptyFolder(albumPath, errorMessage);
74
			} else {
75
				this.viewAlbum(albumPath);
76
			}
77
78
			this._setBackgroundColour();
79
		},
80
81
		/**
82
		 * Starts the slideshow
83
		 *
84
		 * @param {string} path
85
		 * @param {string} albumPath
86
		 */
87
		startSlideshow: function (path, albumPath) {
88
			var album = Gallery.albumMap[albumPath];
89
			var images = album.images;
90
			var startImage = Gallery.imageMap[path];
91
			Gallery.slideShow(images, startImage, false);
92
		},
93
94
		/**
95
		 * Sets up the controls and starts loading the gallery rows
96
		 *
97
		 * @param {string|null} albumPath
98
		 */
99
		viewAlbum: function (albumPath) {
100
			albumPath = albumPath || '';
101
			if (!Gallery.albumMap[albumPath]) {
102
				return;
103
			}
104
105
			this.clear();
106
107
			if (albumPath !== Gallery.currentAlbum
108
				|| (albumPath === Gallery.currentAlbum &&
109
				Gallery.albumMap[albumPath].etag !== Gallery.currentEtag)) {
110
				Gallery.currentAlbum = albumPath;
111
				Gallery.currentEtag = Gallery.albumMap[albumPath].etag;
112
				this._setupButtons(albumPath);
113
			}
114
115
			Gallery.albumMap[albumPath].viewedItems = 0;
116
			Gallery.albumMap[albumPath].preloadOffset = 0;
117
118
			// Each request has a unique ID, so that we can track which request a row belongs to
119
			this.requestId = Math.random();
120
			Gallery.albumMap[Gallery.currentAlbum].requestId = this.requestId;
121
122
			// Loading rows without blocking the execution of the rest of the script
123
			setTimeout(function () {
124
				this.loadVisibleRows.activeIndex = 0;
125
				this.loadVisibleRows(Gallery.albumMap[Gallery.currentAlbum]);
126
			}.bind(this), 0);
127
		},
128
129
		/**
130
		 * Manages the sorting interface
131
		 *
132
		 * @param {string} sortType name or date
133
		 * @param {string} sortOrder asc or des
134
		 */
135
		sortControlsSetup: function (sortType, sortOrder) {
136
			var reverseSortType = 'date';
137
			if (sortType === 'date') {
138
				reverseSortType = 'name';
139
			}
140
			this._setSortButton(sortType, sortOrder, true);
141
			this._setSortButton(reverseSortType, 'asc', false); // default icon
142
		},
143
144
		/**
145
		 * Loads and displays gallery rows on screen
146
		 *
147
		 * view.loadVisibleRows.loading holds the Promise of a row
148
		 *
149
		 * @param {Album} album
150
		 */
151
		loadVisibleRows: function (album) {
152
			var view = this;
153
			// Wait for the previous request to be completed
154
			if (this.loadVisibleRows.processing) {
155
				return;
156
			}
157
158
			/**
159
			 * At this stage, there is no loading taking place, so we can look for new rows
160
			 */
161
162
			var scroll = $('#content-wrapper').scrollTop() + $(window).scrollTop();
163
			// 2 windows worth of rows is the limit from which we need to start loading new rows.
164
			// As we scroll down, it grows
165
			var targetHeight = ($(window).height() * 2) + scroll;
166
			// We throttle rows in order to try and not generate too many CSS resizing events at
167
			// the same time
168
			var showRows = _.throttle(function (album) {
169
170
				// If we've reached the end of the album, we kill the loader
171
				if (!(album.viewedItems < album.subAlbums.length + album.images.length)) {
0 ignored issues
show
Coding Style introduced by
The usage of ! looks confusing here.

The following shows a case which JSHint considers confusing and its respective non-confusing counterpart:

! (str.indexOf(i) > -1) // Bad
str.indexOf(i) === -1 // Good
Loading history...
172
					view.loadVisibleRows.processing = false;
173
					view.loadVisibleRows.loading = null;
174
					return;
175
				}
176
177
				// Prevents creating rows which are no longer required. I.e when changing album
178
				if (view.requestId !== album.requestId) {
179
					return;
180
				}
181
182
				// We can now safely create a new row
183
				var row = album.getRow($(window).width());
184
				var rowDom = row.getDom();
185
				view.element.append(rowDom);
186
187
				return album.fillNextRow(row).then(function () {
188
					if (album.viewedItems < album.subAlbums.length + album.images.length &&
189
						view.element.height() < targetHeight) {
190
						return showRows(album);
191
					}
192
					// No more rows to load at the moment
193
					view.loadVisibleRows.processing = false;
194
					view.loadVisibleRows.loading = null;
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
195
				}, function () {
196
					// Something went wrong, so kill the loader
197
					view.loadVisibleRows.processing = false;
198
					view.loadVisibleRows.loading = null;
199
				});
200
			}, 100);
201
			if (this.element.height() < targetHeight) {
202
				this._showNormal();
203
				this.loadVisibleRows.processing = true;
204
				album.requestId = view.requestId;
205
				this.loadVisibleRows.loading = showRows(album);
206
			}
207
		},
208
209
		/**
210
		 * Shows an empty gallery message
211
		 *
212
		 * @param {string} albumPath
213
		 * @param {string|null} errorMessage
214
		 */
215
		showEmptyFolder: function (albumPath, errorMessage) {
216
			var message = '<div class="icon-gallery"></div>';
217
			var uploadAllowed = true;
218
219
			this.element.children().detach();
220
			this.removeLoading();
221
222
			if (!_.isUndefined(errorMessage) && errorMessage !== null) {
223
				message += '<h2>' + t('gallery',
224
						'Album cannot be shown') + '</h2>';
225
				message += '<p>' + escapeHTML(errorMessage) + '</p>';
0 ignored issues
show
Bug introduced by
escapeHTML does not seem to be defined.
Loading history...
226
				uploadAllowed = false;
227
			} else {
228
				message += '<h2>' + t('gallery',
229
						'No media files found') + '</h2>';
230
				// We can't upload yet on the public side
231
				if (Gallery.token) {
232
					message += '<p>' + t('gallery',
233
							'Upload pictures in the files app to display them here') + '</p>';
234
				} else {
235
					message += '<p>' + t('gallery',
236
							'Upload new files via drag and drop or by using the [+] button above') +
237
						'</p>';
238
				}
239
			}
240
			this.emptyContentElement.html(message);
241
			this.emptyContentElement.removeClass('hidden');
242
243
			this._hideButtons(uploadAllowed);
244
			Gallery.currentAlbum = albumPath;
245
			var availableWidth = $(window).width() - Gallery.buttonsWidth;
246
			this.breadcrumb.init(albumPath, availableWidth);
247
			Gallery.config.albumDesign = null;
248
		},
249
250
		/**
251
		 * Dims the controls bar when retrieving new content. Matches the effect in Files
252
		 */
253
		dimControls: function () {
254
			// Use the existing mask if its already there
255
			var $mask = this.controlsElement.find('.mask');
256
			if ($mask.exists()) {
257
				return;
258
			}
259
			$mask = $('<div class="mask transparent"></div>');
260
			this.controlsElement.append($mask);
261
			$mask.removeClass('transparent');
262
		},
263
264
		/**
265
		 * Shows the infamous loading spinner
266
		 */
267
		showLoading: function () {
268
			this.emptyContentElement.addClass('hidden');
269
			this.controlsElement.removeClass('hidden');
270
			$('#content').addClass('icon-loading');
271
			this.dimControls();
272
		},
273
274
		/**
275
		 * Removes the spinner in the main area and restore normal visibility of the controls bar
276
		 */
277
		removeLoading: function () {
278
			$('#content').removeClass('icon-loading');
279
			this.controlsElement.find('.mask').remove();
280
		},
281
282
		/**
283
		 * Shows thumbnails
284
		 */
285
		_showNormal: function () {
286
			this.emptyContentElement.addClass('hidden');
287
			this.controlsElement.removeClass('hidden');
288
			this.removeLoading();
289
		},
290
291
		/**
292
		 * Sets up our custom handlers for folder uploading operations
293
		 *
294
		 * @see OC.Upload.init/file_upload_param.done()
295
		 *
296
		 * @private
297
		 */
298
		_setupUploader: function () {
299
			var $uploadEl = $('#file_upload_start');
300
			if (!$uploadEl.exists()) {
301
				return;
302
			}
303
			this._uploader = new OC.Uploader($uploadEl, {
304
				fileList: FileList,
0 ignored issues
show
Bug introduced by
The variable FileList seems to be never declared. If this is a global, consider adding a /** global: FileList */ 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...
305
				dropZone: $('#content')
306
			});
307
			this._uploader.on('add', function (e, data) {
308
				data.targetDir = '/' + Gallery.currentAlbum;
309
			});
310
			this._uploader.on('done', function (e, upload) {
311
				var data = upload.data;
312
313
				// is that the last upload ?
314
				if (data.files[0] === data.originalFiles[data.originalFiles.length - 1]) {
315
					var fileList = data.originalFiles;
316
					//Ask for a refresh of the photowall
317
					Gallery.getFiles(Gallery.currentAlbum).done(function () {
318
						var fileId, path;
319
						// Removes the cached thumbnails of files which have been re-uploaded
320
						_(fileList).each(function (fileName) {
321
							path = Gallery.currentAlbum + '/' + fileName;
322
							if (Gallery.imageMap[path]) {
323
								fileId = Gallery.imageMap[path].fileId;
324
								if (Thumbnails.map[fileId]) {
325
									delete Thumbnails.map[fileId];
326
								}
327
							}
328
						});
329
330
						Gallery.view.init(Gallery.currentAlbum);
331
					});
332
				}
333
			});
334
335
			// Since Nextcloud 9.0
336
			if (OC.Uploader) {
337
				OC.Uploader.prototype._isReceivedSharedFile = function (file) {
338
					var path = file.name;
339
					var sharedWith = false;
340
341
					if (Gallery.currentAlbum !== '' && Gallery.currentAlbum !== '/') {
342
						path = Gallery.currentAlbum + '/' + path;
343
					}
344
					if (Gallery.imageMap[path] && Gallery.imageMap[path].sharedWithUser) {
345
						sharedWith = true;
346
					}
347
348
					return sharedWith;
349
				};
350
			}
351
		},
352
353
		/**
354
		 * Adds all the click handlers to buttons the first time they appear in the interface
355
		 *
356
		 * @private
357
		 */
358
		_initButtons: function () {
359
			this.element.on("contextmenu", function(e) { e.preventDefault(); });
360
			$('#filelist-button').click(Gallery.switchToFilesView);
361
			$('#download').click(Gallery.download);
362
			$('#share-button').click(Gallery.share);
363
			Gallery.infoBox = new Gallery.InfoBox();
364
			$('#album-info-button').click(Gallery.showInfo);
365
			$('#sort-name-button').click(Gallery.sorter);
366
			$('#sort-date-button').click(Gallery.sorter);
367
			$('#save #save-button').click(Gallery.showSaveForm);
368
			$('.save-form').submit(Gallery.saveForm);
369
			this._renderNewButton();
370
			// Trigger cancelling of file upload
371
			$('#uploadprogresswrapper .stop').on('click', function () {
372
				OC.Upload.cancelUploads();
373
			});
374
			this.requestId = Math.random();
375
		},
376
377
		/**
378
		 * Sets up all the buttons of the interface and the breadcrumbs
379
		 *
380
		 * @param {string} albumPath
381
		 * @private
382
		 */
383
		_setupButtons: function (albumPath) {
384
			this._shareButtonSetup(albumPath);
385
			this._infoButtonSetup();
386
387
			var availableWidth = $(window).width() - Gallery.buttonsWidth;
388
			this.breadcrumb.init(albumPath, availableWidth);
389
			var album = Gallery.albumMap[albumPath];
390
			
391
			var sum = album.images.length + album.subAlbums.length;
392
			//If sum of the number of images and subalbums exceeds 1 then show the buttons.
393
			if(sum > 1)
394
			{
395
				$('#sort-name-button').show();
396
				$('#sort-date-button').show();
397
			}
398
			else
399
			{
400
				$('#sort-name-button').hide();
401
				$('#sort-date-button').hide();
402
			}
403
			var currentSort = Gallery.config.albumSorting;
404
			this.sortControlsSetup(currentSort.type, currentSort.order);
405
			Gallery.albumMap[Gallery.currentAlbum].images.sort(
406
				Gallery.utility.sortBy(currentSort.type,
407
					currentSort.order));
408
			Gallery.albumMap[Gallery.currentAlbum].subAlbums.sort(Gallery.utility.sortBy('name',
409
				currentSort.albumOrder));
410
411
			$('#save-button').show();
412
			$('#download').show();
413
			$('a.button.new').show();
414
		},
415
416
		/**
417
		 * Hide buttons in the controls bar
418
		 *
419
		 * @param uploadAllowed
420
		 */
421
		_hideButtons: function (uploadAllowed) {
422
			$('#album-info-button').hide();
423
			$('#share-button').hide();
424
			$('#sort-name-button').hide();
425
			$('#sort-date-button').hide();
426
			$('#save-button').hide();
427
			$('#download').hide();
428
429
			if (!uploadAllowed) {
430
				$('a.button.new').hide();
431
			}
432
		},
433
434
		/**
435
		 * Shows or hides the share button depending on if we're in a public gallery or not
436
		 *
437
		 * @param {string} albumPath
438
		 * @private
439
		 */
440
		_shareButtonSetup: function (albumPath) {
441
			var shareButton = $('#share-button');
442
			if (albumPath === '' || Gallery.token) {
443
				shareButton.hide();
444
			} else {
445
				shareButton.show();
446
			}
447
		},
448
449
		/**
450
		 * Shows or hides the info button based on the information we've received from the server
451
		 *
452
		 * @private
453
		 */
454
		_infoButtonSetup: function () {
455
			var infoButton = $('#album-info-button');
456
			infoButton.find('span').hide();
457
			var infoContentContainer = $('.album-info-container');
458
			infoContentContainer.slideUp();
459
			infoContentContainer.css('max-height',
460
				$(window).height() - Gallery.browserToolbarHeight);
461
			var albumInfo = Gallery.config.albumInfo;
462
			if (Gallery.config.albumError) {
463
				infoButton.hide();
464
				var text = '<strong>' + t('gallery', 'Configuration error') + '</strong></br>' +
465
					Gallery.config.albumError.message + '</br></br>';
466
				Gallery.utility.showHtmlNotification(text, 7);
467
			} else if ($.isEmptyObject(albumInfo)) {
468
				infoButton.hide();
469
			} else {
470
				infoButton.show();
471
				if (albumInfo.inherit !== 'yes' || albumInfo.level === 0) {
472
					infoButton.find('span').delay(1000).slideDown();
473
				}
474
			}
475
		},
476
477
		/**
478
		 * Sets the background colour of the photowall
479
		 *
480
		 * @private
481
		 */
482
		_setBackgroundColour: function () {
483
			var wrapper = $('#content-wrapper');
484
			var albumDesign = Gallery.config.albumDesign;
485
			if (!$.isEmptyObject(albumDesign) && albumDesign.background) {
486
				wrapper.css('background-color', albumDesign.background);
487
			} else {
488
				wrapper.css('background-color', '#fff');
489
			}
490
		},
491
492
		/**
493
		 * Picks the image which matches the sort order
494
		 *
495
		 * @param {string} sortType name or date
496
		 * @param {string} sortOrder asc or des
497
		 * @param {boolean} active determines if we're setting up the active sort button
498
		 * @private
499
		 */
500
		_setSortButton: function (sortType, sortOrder, active) {
501
			var button = $('#sort-' + sortType + '-button');
502
			// Removing all the classes which control the image in the button
503
			button.removeClass('active-button');
504
			button.find('img').removeClass('front');
505
			button.find('img').removeClass('back');
506
507
			// We need to determine the reverse order in order to send that image to the back
508
			var reverseSortOrder = 'des';
509
			if (sortOrder === 'des') {
510
				reverseSortOrder = 'asc';
511
			}
512
513
			// We assign the proper order to the button images
514
			button.find('img.' + sortOrder).addClass('front');
515
			button.find('img.' + reverseSortOrder).addClass('back');
516
517
			// The active button needs a hover action for the flip effect
518
			if (active) {
519
				button.addClass('active-button');
520
				if (button.is(":hover")) {
521
					button.removeClass('hover');
522
				}
523
				// We can't use a toggle here
524
				button.hover(function () {
525
						$(this).addClass('hover');
526
					},
527
					function () {
528
						$(this).removeClass('hover');
529
					});
530
			}
531
		},
532
		
533
		/**
534
		 * If no url is entered then do not show the error box.
535
		 *
536
		 */
537
		_blankUrl: function() {
538
			$('#remote_address').on("change keyup paste", function() {
539
 				if ($(this).val() === '') {
540
 					$('#save-button-confirm').prop('disabled', true);
541
 				} else {
542
 					$('#save-button-confirm').prop('disabled', false);
543
 				}
544
			});
545
		},
546
		
547
		/**
548
		 * Creates the [+] button allowing users who can't drag and drop to upload files
549
		 *
550
		 * @see core/apps/files/js/filelist.js
551
		 * @private
552
		 */
553
		_renderNewButton: function () {
554
			// if no actions container exist, skip
555
			var $actionsContainer = $('.actions');
556
			if (!$actionsContainer.length) {
557
				return;
558
			}
559
			if (!this._addButtonTemplate) {
560
				this._addButtonTemplate = Handlebars.compile(TEMPLATE_ADDBUTTON);
561
			}
562
			var $newButton = $(this._addButtonTemplate({
563
				addText: t('gallery', 'New'),
564
				iconUrl: OC.imagePath('core', 'actions/add')
565
			}));
566
567
			$actionsContainer.prepend($newButton);
568
			$newButton.tooltip({'placement': 'bottom'});
569
570
			$newButton.click(_.bind(this._onClickNewButton, this));
571
			this._newButton = $newButton;
572
		},
573
574
		/**
575
		 * Creates the click handler for the [+] button
576
		 * @param event
577
		 * @returns {boolean}
578
		 *
579
		 * @see core/apps/files/js/filelist.js
580
		 * @private
581
		 */
582
		_onClickNewButton: function (event) {
583
			var $target = $(event.target);
584
			if (!$target.hasClass('.button')) {
585
				$target = $target.closest('.button');
586
			}
587
			this._newButton.tooltip('hide');
588
			event.preventDefault();
589
			if ($target.hasClass('disabled')) {
590
				return false;
591
			}
592
			if (!this._newFileMenu) {
593
				this._newFileMenu = new Gallery.NewFileMenu();
594
				$('.actions').append(this._newFileMenu.$el);
595
			}
596
			this._newFileMenu.showAt($target);
597
598
			if (Gallery.currentAlbum === '') {
599
				$('.menuitem[data-action="hideAlbum"]').parent().hide();
600
			}
601
			return false;
602
		}
603
	};
604
605
	Gallery.View = View;
606
})(jQuery, _, OC, t, Gallery);
607