Completed
Push — stable9 ( 41758a...bf53b6 )
by Lukas
15:58 queued 12:53
created

View._isValidPath   A

Complexity

Conditions 3
Paths 5

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
c 1
b 0
f 0
nc 5
nop 1
dl 0
loc 11
rs 9.4285
1
/* global Gallery */
2
(function ($, _, OC, t, Gallery) {
3
	"use strict";
4
	/**
5
	 * Builds and updates the Gallery view
6
	 *
7
	 * @constructor
8
	 */
9
	var View = function () {
10
		this.element = $('#gallery');
11
		this.loadVisibleRows.loading = false;
12
		this.breadcrumb = new Gallery.Breadcrumb();
13
	};
14
15
	View.prototype = {
16
		element: null,
17
		breadcrumb: null,
18
		requestId: -1,
19
20
		/**
21
		 * Removes all thumbnails from the view
22
		 */
23
		clear: function () {
24
			// We want to keep all the events
25
			this.element.children().detach();
26
			Gallery.showLoading();
27
		},
28
29
		/**
30
		 * @param {string} path
31
		 * @returns {boolean}
32
		 */
33
		_isValidPath: function(path) {
34
			var sections = path.split('/');
35
			for (var i = 0; i < sections.length; i++) {
36
				if (sections[i] === '..') {
37
					return false;
38
				}
39
			}
40
41
			return path.toLowerCase().indexOf(decodeURI('%0a')) === -1 &&
42
				path.toLowerCase().indexOf(decodeURI('%00')) === -1;
43
		},
44
45
		/**
46
		 * Populates the view if there are images or albums to show
47
		 *
48
		 * @param {string} albumPath
49
		 */
50
		init: function (albumPath) {
51
			// Set path to an empty value if not a valid one
52
			if(!this._isValidPath(albumPath)) {
53
				albumPath = '';
54
			}
55
56
			// Only do it when the app is initialised
57
			if (this.requestId === -1) {
58
				this._initButtons();
59
			}
60
			if ($.isEmptyObject(Gallery.imageMap)) {
61
				this.clear();
62
				if (albumPath === '') {
63
					Gallery.showEmpty();
64
				} else {
65
					Gallery.showEmptyFolder();
66
					this.hideButtons();
67
					Gallery.currentAlbum = albumPath;
68
					var availableWidth = $(window).width() - Gallery.buttonsWidth;
69
					this.breadcrumb.init(albumPath, availableWidth);
70
					Gallery.config.albumDesign = null;
71
				}
72
			} else {
73
				this.viewAlbum(albumPath);
74
			}
75
76
			this._setBackgroundColour();
77
		},
78
79
		/**
80
		 * Starts the slideshow
81
		 *
82
		 * @param {string} path
83
		 * @param {string} albumPath
84
		 */
85
		startSlideshow: function (path, albumPath) {
86
			var album = Gallery.albumMap[albumPath];
87
			var images = album.images;
88
			var startImage = Gallery.imageMap[path];
89
			Gallery.slideShow(images, startImage, false);
90
		},
91
92
		/**
93
		 * Sets up the controls and starts loading the gallery rows
94
		 *
95
		 * @param {string|null} albumPath
96
		 */
97
		viewAlbum: function (albumPath) {
98
			albumPath = albumPath || '';
99
			if (!Gallery.albumMap[albumPath]) {
100
				return;
101
			}
102
103
			this.clear();
104
			$('#loading-indicator').show();
105
106
			if (albumPath !== Gallery.currentAlbum) {
107
				this.loadVisibleRows.loading = false;
108
				Gallery.currentAlbum = albumPath;
109
				this._setupButtons(albumPath);
110
			}
111
112
			Gallery.albumMap[albumPath].viewedItems = 0;
113
			Gallery.albumMap[albumPath].preloadOffset = 0;
114
115
			// Each request has a unique ID, so that we can track which request a row belongs to
116
			this.requestId = Math.random();
117
			Gallery.albumMap[Gallery.currentAlbum].requestId = this.requestId;
118
119
			// Loading rows without blocking the execution of the rest of the script
120
			setTimeout(function () {
121
				this.loadVisibleRows.activeIndex = 0;
122
				this.loadVisibleRows(Gallery.albumMap[Gallery.currentAlbum], Gallery.currentAlbum);
123
			}.bind(this), 0);
124
		},
125
126
		/**
127
		 * Manages the sorting interface
128
		 *
129
		 * @param {string} sortType name or date
130
		 * @param {string} sortOrder asc or des
131
		 */
132
		sortControlsSetup: function (sortType, sortOrder) {
133
			var reverseSortType = 'date';
134
			if (sortType === 'date') {
135
				reverseSortType = 'name';
136
			}
137
			this._setSortButton(sortType, sortOrder, true);
138
			this._setSortButton(reverseSortType, 'asc', false); // default icon
139
		},
140
141
		/**
142
		 * Loads and displays gallery rows on screen
143
		 *
144
		 * @param {Album} album
145
		 * @param {string} path
146
		 *
147
		 * @returns {boolean|null|*}
148
		 */
149
		loadVisibleRows: function (album, path) {
150
			var view = this;
151
			// If the row is still loading (state() = 'pending'), let it load
152
			if (this.loadVisibleRows.loading &&
153
				this.loadVisibleRows.loading.state() !== 'resolved') {
154
				return this.loadVisibleRows.loading;
155
			}
156
157
			/**
158
			 * At this stage, there is no loading taking place (loading = false|null), so we can
159
			 * 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
			var showRows = function (album) {
167
168
				// If we've reached the end of the album, we kill the loader
169
				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...
170
					view.loadVisibleRows.loading = null;
171
					$('#loading-indicator').hide();
172
					return;
173
				}
174
175
				// Everything is still in sync, since no deferred calls have been placed yet
176
177
				return album.getNextRow($(window).width()).then(function (row) {
178
179
					/**
180
					 * At this stage, the row has a width and contains references to images based
181
					 * on
182
					 * information available when making the request, but this information may have
183
					 * changed while we were receiving thumbnails for the row
184
					 */
185
186
					if (view.requestId === row.requestId) {
187
						return row.getDom().then(function (dom) {
188
189
							if (Gallery.currentAlbum !== path) {
190
								view.loadVisibleRows.loading = null;
191
								return; //throw away the row if the user has navigated away in the
192
										// meantime
193
							}
194
							if (view.element.length === 1) {
195
								Gallery.showNormal();
196
							}
197
198
							view.element.append(dom);
199
200
							if (album.viewedItems < album.subAlbums.length + album.images.length &&
201
								view.element.height() < targetHeight) {
202
								return showRows(album);
203
							}
204
205
							// No more rows to load at the moment
206
							view.loadVisibleRows.loading = null;
207
							$('#loading-indicator').hide();
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...
208
						}, function () {
209
							// Something went wrong, so kill the loader
210
							view.loadVisibleRows.loading = null;
211
							$('#loading-indicator').hide();
212
						});
213
					}
214
					// This is the safest way to do things
215
					view.viewAlbum(Gallery.currentAlbum);
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...
216
217
				});
218
			};
219
			if (this.element.height() < targetHeight) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if this.element.height() < targetHeight is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
220
				this.loadVisibleRows.loading = true;
221
				this.loadVisibleRows.loading = showRows(album);
222
				return this.loadVisibleRows.loading;
223
			}
224
		},
225
226
		hideButtons: function () {
227
			$('#loading-indicator').hide();
228
			$('#album-info-button').hide();
229
			$('#share-button').hide();
230
			$('#sort-name-button').hide();
231
			$('#sort-date-button').hide();
232
		},
233
234
		/**
235
		 * Adds all the click handlers to buttons the first time they appear in the interface
236
		 *
237
		 * @private
238
		 */
239
		_initButtons: function () {
240
			$('#filelist-button').click(Gallery.switchToFilesView);
241
			$('#download').click(Gallery.download);
242
			$('#share-button').click(Gallery.share);
243
			Gallery.infoBox = new Gallery.InfoBox();
244
			$('#album-info-button').click(Gallery.showInfo);
245
			$('#sort-name-button').click(Gallery.sorter);
246
			$('#sort-date-button').click(Gallery.sorter);
247
			$('#save #save-button').click(Gallery.showSaveForm);
248
			$('.save-form').submit(Gallery.saveForm);
249
250
			this.requestId = Math.random();
251
		},
252
253
		/**
254
		 * Sets up all the buttons of the interface and the breadcrumbs
255
		 *
256
		 * @param {string} albumPath
257
		 * @private
258
		 */
259
		_setupButtons: function (albumPath) {
260
			this._shareButtonSetup(albumPath);
261
			this._infoButtonSetup();
262
263
			var availableWidth = $(window).width() - Gallery.buttonsWidth;
264
			this.breadcrumb.init(albumPath, availableWidth);
265
266
			$('#sort-name-button').show();
267
			$('#sort-date-button').show();
268
			var currentSort = Gallery.config.albumSorting;
269
			this.sortControlsSetup(currentSort.type, currentSort.order);
270
			Gallery.albumMap[Gallery.currentAlbum].images.sort(
271
				Gallery.utility.sortBy(currentSort.type,
272
					currentSort.order));
273
			Gallery.albumMap[Gallery.currentAlbum].subAlbums.sort(Gallery.utility.sortBy('name',
274
				currentSort.albumOrder));
275
		},
276
277
		/**
278
		 * Shows or hides the share button depending on if we're in a public gallery or not
279
		 *
280
		 * @param {string} albumPath
281
		 * @private
282
		 */
283
		_shareButtonSetup: function (albumPath) {
284
			var shareButton = $('#share-button');
285
			if (albumPath === '' || Gallery.token) {
286
				shareButton.hide();
287
			} else {
288
				shareButton.show();
289
			}
290
		},
291
292
		/**
293
		 * Shows or hides the info button based on the information we've received from the server
294
		 *
295
		 * @private
296
		 */
297
		_infoButtonSetup: function () {
298
			var infoButton = $('#album-info-button');
299
			infoButton.find('span').hide();
300
			var infoContentContainer = $('.album-info-container');
301
			infoContentContainer.slideUp();
302
			infoContentContainer.css('max-height',
303
				$(window).height() - Gallery.browserToolbarHeight);
304
			var albumInfo = Gallery.config.albumInfo;
305
			if (Gallery.config.albumError) {
306
				infoButton.hide();
307
				var text = '<strong>' + t('gallery', 'Configuration error') + '</strong></br>' +
308
					Gallery.config.albumError.message + '</br></br>';
309
				Gallery.utility.showHtmlNotification(text, 7);
310
			} else if ($.isEmptyObject(albumInfo)) {
311
				infoButton.hide();
312
			} else {
313
				infoButton.show();
314
				if (albumInfo.inherit !== 'yes' || albumInfo.level === 0) {
315
					infoButton.find('span').delay(1000).slideDown();
316
				}
317
			}
318
		},
319
320
		/**
321
		 * Sets the background colour of the photowall
322
		 *
323
		 * @private
324
		 */
325
		_setBackgroundColour: function () {
326
			var wrapper = $('#content-wrapper');
327
			var albumDesign = Gallery.config.albumDesign;
328
			if (!$.isEmptyObject(albumDesign) && albumDesign.background) {
329
				wrapper.css('background-color', albumDesign.background);
330
			} else {
331
				wrapper.css('background-color', '#fff');
332
			}
333
		},
334
335
		/**
336
		 * Picks the image which matches the sort order
337
		 *
338
		 * @param {string} sortType name or date
339
		 * @param {string} sortOrder asc or des
340
		 * @param {boolean} active determines if we're setting up the active sort button
341
		 * @private
342
		 */
343
		_setSortButton: function (sortType, sortOrder, active) {
344
			var button = $('#sort-' + sortType + '-button');
345
			// Removing all the classes which control the image in the button
346
			button.removeClass('active-button');
347
			button.find('img').removeClass('front');
348
			button.find('img').removeClass('back');
349
350
			// We need to determine the reverse order in order to send that image to the back
351
			var reverseSortOrder = 'des';
352
			if (sortOrder === 'des') {
353
				reverseSortOrder = 'asc';
354
			}
355
356
			// We assign the proper order to the button images
357
			button.find('img.' + sortOrder).addClass('front');
358
			button.find('img.' + reverseSortOrder).addClass('back');
359
360
			// The active button needs a hover action for the flip effect
361
			if (active) {
362
				button.addClass('active-button');
363
				if (button.is(":hover")) {
364
					button.removeClass('hover');
365
				}
366
				// We can't use a toggle here
367
				button.hover(function () {
368
						$(this).addClass('hover');
369
					},
370
					function () {
371
						$(this).removeClass('hover');
372
					});
373
			}
374
		}
375
	};
376
377
	Gallery.View = View;
378
})(jQuery, _, OC, t, Gallery);
379