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++; |
|
|
|
|
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
|
|
|
|