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 |
||
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)) { |
||
0 ignored issues
–
show
|
|||
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 |
||
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 |
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
The function
isBig
will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly returnundefined
.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.