Completed
Pull Request — master (#426)
by
unknown
02:16
created

SlideShow.show   C

Complexity

Conditions 9
Paths 13

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 22
rs 6.412
c 1
b 0
f 0
cc 9
nc 13
nop 1
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 Gallery, Thumbnails, DOMPurify */
13
(function ($, OC, OCA, t) {
14
	"use strict";
15
	/**
16
	 * Slideshow featuring zooming
17
	 *
18
	 * @constructor
19
	 */
20
	var SlideShow = function () {
21
	};
22
23
	SlideShow.prototype = {
24
		slideshowTemplate: null,
25
		container: null,
26
		zoomablePreviewContainer: null,
27
		controls: null,
28
		imageCache: {},
29
		/** {Image} */
30
		currentImage: null,
31
		errorLoadingImage: false,
32
		onStop: null,
33
		zoomablePreview: null,
34
		active: false,
35
		backgroundToggle: false,
36
		// We need 6 hexas for comparison reasons
37
		darkBackgroundColour: '#000000',
38
		lightBackgroundColour: '#ffffff',
39
40
		/**
41
		 * Initialises the slideshow
42
		 *
43
		 * @param {boolean} autoPlay
44
		 * @param {number} interval
45
		 * @param {Array} features
46
		 */
47
		init: function (autoPlay, interval, features) {
48
			if (features.indexOf('background_colour_toggle') > -1) {
49
				this.backgroundToggle = true;
50
			}
51
52
			return $.when(this._getSlideshowTemplate()).then(function ($tmpl) {
53
				// Move the slideshow outside the content so we can hide the content
54
				$('body').append($tmpl);
55
				this.container = $('#slideshow');
56
				this.zoomablePreviewContainer = this.container.find('.bigshotContainer');
57
				this.zoomablePreview = new SlideShow.ZoomablePreview(this.container);
58
				this.controls =
59
					new SlideShow.Controls(
60
						this,
61
						this.container,
62
						this.zoomablePreview,
63
						interval,
64
						features);
65
				this.controls.init();
66
67
				this._initControlsAutoFader();
68
69
				// Only modern browsers can manipulate history
70
				if (history && history.pushState) {
0 ignored issues
show
Best Practice introduced by
If you intend to check if the variable history is declared in the current environment, consider using typeof history === "undefined" instead. This is safe if the variable is not actually declared.
Loading history...
71
					// Stop the slideshow when backing out.
72
					$(window).bind('popstate.slideshow', function () {
73
						if (this.active === true) {
74
							this.active = false;
75
							this.controls.stop();
76
						}
77
					}.bind(this));
78
				}
79
			}.bind(this)).fail(function () {
80
				OC.Notification.show(t('gallery', 'Error loading slideshow template'));
81
			});
82
		},
83
84
		/**
85
		 * Refreshes the slideshow's data
86
		 *
87
		 * @param {{name:string, url: string, path: string, fallBack: string}[]} images
88
		 * @param {boolean} autoPlay
89
		 */
90
		setImages: function (images, autoPlay) {
91
			this._hideImage();
92
			this.images = images;
93
			this.controls.update(images, autoPlay);
94
		},
95
96
		/**
97
		 * Launches the slideshow
98
		 *
99
		 * @param {number} index
100
		 *
101
		 * @returns {*}
102
		 */
103
		show: function (index) {
104
			this.hideErrorNotification();
105
			this.active = true;
106
			this.container.show();
107
			this.container.css('background-position', 'center');
108
			this._hideImage();
109
			this.container.find('.icon-loading-dark').show();
110
			var currentImageId = index;
111
			return this.loadImage(this.images[index]).then(function (img) {
112
				this.container.css('background-position', '-10000px 0');
113
114
				// check if we moved along while we were loading
115
				if (currentImageId === index) {
116
				    if (this.images[index].mimeType === 'image/jpeg' || this.images[index].mimeType === 'image/tiff') {
117
                                        // check if not in cache
118
                                        if (this.images[index].desc===undefined) {
119
					    if (window.galleryFileAction) {
120
						var url = window.galleryFileAction.buildGalleryUrl('files', '/exif/'+this.images[index].fileId,{});
0 ignored issues
show
Coding Style introduced by
Line is too long.
Loading history...
121
					    } else {
122
						var url = window.Gallery.utility.buildGalleryUrl('files', '/exif/'+this.images[index].fileId,{});
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable url already seems to be declared on line 120. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
Coding Style introduced by
Line is too long.
Loading history...
Bug introduced by
It seems like url was already defined.
Loading history...
123
					    }
124
                                            $.getJSON(url).then(function(data) {
0 ignored issues
show
Bug introduced by
The variable url seems to be used out of scope.

This error can usually be fixed by declaring the variable in the scope where it is used:

function someFunction() {
    (function() {
        var i = 0;
    })();

    // i is not defined.
    alert(i);
}

// This can be fixed by moving the var statement to the outer scope.

function someFunction() {
    var i;
    (function() {
        i = 1;
    })();

    alert(i);
};
Loading history...
125
                                                var desc;
126
                                                if (data) {
127
                                                    // IPTC:Description (Picasa, Photoshop, Lightroom)
128
                                                    if (data['iptc']&&data['iptc']['APP13']&&data['iptc']['APP13']['2#120']) {
0 ignored issues
show
Coding Style introduced by
Line is too long.
Loading history...
Coding Style introduced by
['iptc'] could be written in dot notation.

You can rewrite this statement in dot notation:

var obj = { };
obj['foo'] = 'bar'; // Bad
obj.foo = 'bar'; // Good
Loading history...
Coding Style introduced by
['APP13'] could be written in dot notation.

You can rewrite this statement in dot notation:

var obj = { };
obj['foo'] = 'bar'; // Bad
obj.foo = 'bar'; // Good
Loading history...
129
                                                        desc=data['iptc']['APP13']['2#120'][0];
0 ignored issues
show
Coding Style introduced by
['iptc'] could be written in dot notation.

You can rewrite this statement in dot notation:

var obj = { };
obj['foo'] = 'bar'; // Bad
obj.foo = 'bar'; // Good
Loading history...
Coding Style introduced by
['APP13'] could be written in dot notation.

You can rewrite this statement in dot notation:

var obj = { };
obj['foo'] = 'bar'; // Bad
obj.foo = 'bar'; // Good
Loading history...
130
                                                    }
131
                                                    // EXIF:Description (old camera model)
132
                                                    if (!desc) {
133
                                                        if (data['exif']&&data['exif']['ImageDescription'])
0 ignored issues
show
Coding Style introduced by
['exif'] could be written in dot notation.

You can rewrite this statement in dot notation:

var obj = { };
obj['foo'] = 'bar'; // Bad
obj.foo = 'bar'; // Good
Loading history...
Coding Style introduced by
['ImageDescription'] could be written in dot notation.

You can rewrite this statement in dot notation:

var obj = { };
obj['foo'] = 'bar'; // Bad
obj.foo = 'bar'; // Good
Loading history...
134
                                                        desc=data['exif']['ImageDescription'];
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
Bug introduced by
{ was expected, but instead desc was given.
Loading history...
Coding Style introduced by
['exif'] could be written in dot notation.

You can rewrite this statement in dot notation:

var obj = { };
obj['foo'] = 'bar'; // Bad
obj.foo = 'bar'; // Good
Loading history...
Coding Style introduced by
['ImageDescription'] could be written in dot notation.

You can rewrite this statement in dot notation:

var obj = { };
obj['foo'] = 'bar'; // Bad
obj.foo = 'bar'; // Good
Loading history...
135
                                                    }
136
                                                    if (desc) {
137
                                                        this.images[index].desc=desc;
138
                                                        this.controls.show(currentImageId);
139
                                                        this._initControlsAutoFader();
140
                                                        this.container.removeClass('inactive');
141
                                                    } else {
142
                                                        this.images[index].desc='';
143
                                                    }
144
                                                }
145
                                            }.bind(this));
146
                                        } else if (this.images[index].desc) {
147
                                                    this.controls.show(currentImageId);
148
                                                    this._initControlsAutoFader();
149
                                                    this.container.removeClass('inactive');
150
                                        }
151
                                        }
152
					var image = this.images[index];
153
					var transparent = this._isTransparent(image.mimeType);
154
					this.controls.showActionButtons(transparent, Gallery.token, image.permissions);
155
					this.errorLoadingImage = false;
156
					this.currentImage = img;
157
					img.setAttribute('alt', image.name);
158
					$(img).css('position', 'absolute');
159
					$(img).css('background-color', image.backgroundColour);
160
					if (transparent && this.backgroundToggle === true) {
161
						var $border = 30 / window.devicePixelRatio;
162
						$(img).css('outline', $border + 'px solid ' + image.backgroundColour);
163
					}
164
165
					this.zoomablePreview.startBigshot(img, this.currentImage, image.mimeType);
166
167
					this._setUrl(image.path);
168
					this.controls.show(currentImageId);
169
					this.container.find('.icon-loading-dark').hide();
170
				}
171
			}.bind(this), function () {
172
				// Don't do anything if the user has moved along while we were loading as it would
173
				// mess up the index
174
				if (currentImageId === index) {
175
					this.errorLoadingImage = true;
176
					this.showErrorNotification(null);
177
					this._setUrl(this.images[index].path);
178
					this.images.splice(index, 1);
179
					this.controls.updateControls(this.images, this.errorLoadingImage);
180
				}
181
			}.bind(this));
182
		},
183
184
		/**
185
		 * Loads the image to show in the slideshow and preloads the next one
186
		 *
187
		 * @param {Object} preview
188
		 *
189
		 * @returns {*}
190
		 */
191
		loadImage: function (preview) {
192
			var url = preview.url;
193
			var mimeType = preview.mimeType;
194
195
			if (!this.imageCache[url]) {
196
				this.imageCache[url] = new $.Deferred();
197
				var image = new Image();
0 ignored issues
show
Bug introduced by
The variable Image seems to be never declared. If this is a global, consider adding a /** global: Image */ 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...
198
199
				image.onload = function () {
200
					preview.backgroundColour = this._getBackgroundColour(image, mimeType);
201
					if (this.imageCache[url]) {
202
						this.imageCache[url].resolve(image);
203
					}
204
				}.bind(this);
205
				image.onerror = function () {
206
					if (this.imageCache[url]) {
207
						this.imageCache[url].reject(url);
208
					}
209
				}.bind(this);
210
				if (mimeType === 'image/svg+xml') {
211
					image.src = this._getSVG(url);
212
				} else {
213
					image.src = url;
214
				}
215
			}
216
			return this.imageCache[url];
217
		},
218
219
		/**
220
		 * Shows a new image in the slideshow and preloads the next in the list
221
		 *
222
		 * @param {number} current
223
		 * @param {Object} next
224
		 */
225
		next: function (current, next) {
226
			this.show(current).then(function () {
227
				// Preloads the next image in the list
228
				this.loadImage(next);
229
			}.bind(this));
230
		},
231
232
		/**
233
		 * Determines which colour to use for the background
234
		 *
235
		 * @param {*} image
236
		 * @param {string} mimeType
237
		 *
238
		 * @returns {string}
239
		 * @private
240
		 */
241
		_getBackgroundColour: function (image, mimeType) {
242
			var backgroundColour = this.darkBackgroundColour;
243
			if (this._isTransparent(mimeType) && this._isMainlyDark(image)) {
244
				backgroundColour = this.lightBackgroundColour;
245
			}
246
			return backgroundColour;
247
		},
248
249
		/**
250
		 * Calculates the luminance of an image to determine if an image is mainly dark
251
		 *
252
		 * @param {*} image
253
		 *
254
		 * @returns {boolean}
255
		 * @private
256
		 */
257
		_isMainlyDark: function (image) {
258
			var isMainlyDark = false;
259
			var numberOfSamples = 1000; // Seems to be the sweet spot
260
			// The name has to be 'canvas'
261
			var lumiCanvas = document.createElement('canvas');
262
263
			var imgArea = image.width * image.height;
264
			var canArea = numberOfSamples;
265
			var factor = Math.sqrt(canArea / imgArea);
266
267
			var scaledWidth = factor * image.width;
268
			var scaledHeight = factor * image.height;
269
			lumiCanvas.width = scaledWidth;
270
			lumiCanvas.height = scaledHeight;
271
			var lumiCtx = lumiCanvas.getContext('2d');
272
			lumiCtx.drawImage(image, 0, 0, scaledWidth, scaledHeight);
273
			var imgData = lumiCtx.getImageData(0, 0, lumiCanvas.width, lumiCanvas.height);
274
			var pix = imgData.data; // pix.length will be approximately 4*numberOfSamples (for RGBA)
275
			var pixelArraySize = pix.length;
276
			var totalLuminance = 0;
277
			var sampleNumber = 1;
278
			var averageLuminance;
279
			var totalAlpha = 0;
280
			var alphaLevel;
281
			var red = 0;
282
			var green = 0;
283
			var blue = 0;
284
			var alpha = 0;
285
			var lum = 0;
286
			var alphaThreshold = 0.1;
287
288
			var sampleCounter = 0;
289
			var itemsPerPixel = 4; // red, green, blue, alpha
290
			// i += 4 because 4 colours for every pixel
291
			for (var i = 0, n = pixelArraySize; i < n; i += itemsPerPixel) {
292
				sampleCounter++;
293
				alpha = pix[i + 3] / 255;
294
				totalAlpha += alpha;
295
				if (Math.ceil(alpha * 100) / 100 > alphaThreshold) {
296
					red = pix[i];
297
					green = pix[i + 1];
298
					blue = pix[i + 2];
299
					// Luminance formula from
300
					// http://stackoverflow.com/questions/596216/formula-to-determine-brightness-of-rgb-color
301
					lum = (red + red + green + green + green + blue) / 6;
302
					//lum = (red * 0.299 + green * 0.587 + blue * 0.114 );
303
					totalLuminance += lum * alpha;
304
					sampleNumber++;
305
				}
306
			}
307
308
			// Deletes the canvas
309
			lumiCanvas = null;
0 ignored issues
show
Unused Code introduced by
The assignment to lumiCanvas seems to be never used. If you intend to free memory here, this is not necessary since the variable leaves the scope anyway.
Loading history...
310
311
			// Calculate the optimum background colour for this image
312
			averageLuminance = Math.ceil((totalLuminance / sampleNumber) * 100) / 100;
313
			alphaLevel = Math.ceil((totalAlpha / numberOfSamples) * 100);
314
315
			if (averageLuminance < 60 && alphaLevel < 90) {
316
				isMainlyDark = true;
317
			}
318
319
			return isMainlyDark;
320
		},
321
322
		/**
323
		 * Stops the slideshow
324
		 */
325
		stop: function () {
326
			this.active = false;
327
			this.images = null;
328
			this._hideImage();
329
			if (this.onStop) {
330
				this.onStop();
331
			}
332
		},
333
334
		/**
335
		 * Sends the current image as a download
336
		 *
337
		 * @param {string} downloadUrl
338
		 *
339
		 * @returns {boolean}
340
		 */
341
		getImageDownload: function (downloadUrl) {
342
			OC.redirect(downloadUrl);
343
			return false;
344
		},
345
346
		/**
347
		 * Changes the colour of the background of the image
348
		 */
349
		toggleBackground: function () {
350
			var toHex = function (x) {
351
				return ("0" + parseInt(x).toString(16)).slice(-2);
352
			};
353
			var container = this.zoomablePreviewContainer.children('img');
354
			var rgb = container.css('background-color').match(/\d+/g);
355
			var hex = "#" + toHex(rgb[0]) + toHex(rgb[1]) + toHex(rgb[2]);
356
			var $border = 30 / window.devicePixelRatio;
357
			var newBackgroundColor;
358
359
			// Grey #363636
360
			if (hex === this.darkBackgroundColour) {
361
				newBackgroundColor = this.lightBackgroundColour;
362
			} else {
363
				newBackgroundColor = this.darkBackgroundColour;
364
			}
365
366
			container.css('background-color', newBackgroundColor);
367
			if (this.backgroundToggle === true) {
368
				container.css('outline', $border + 'px solid ' + newBackgroundColor);
369
			}
370
		},
371
372
		/**
373
		 * Shows an error notification
374
		 *
375
		 * @param {string} message
376
		 */
377
		showErrorNotification: function (message) {
378
			if ($.isEmptyObject(message)) {
379
				message = t('gallery',
380
					'<strong>Error!</strong> Could not generate a preview of this file.<br>' +
381
					'Please go to the next slide while we remove this image from the slideshow');
382
			}
383
			this.container.find('.notification').html(message);
384
			this.container.find('.notification').show();
385
			this.controls.hideButton('.changeBackground');
386
		},
387
388
		/**
389
		 * Hides the error notification
390
		 */
391
		hideErrorNotification: function () {
392
			this.container.find('.notification').hide();
393
			this.container.find('.notification').html('');
394
		},
395
396
		/**
397
		 * Removes a specific button from the interface
398
		 *
399
		 * @param button
400
		 */
401
		removeButton: function (button) {
402
			this.controls.removeButton(button);
403
		},
404
405
		/**
406
		 * Deletes an image from the slideshow
407
		 *
408
		 * @param {object} image
409
		 * @param {number} currentIndex
410
		 */
411
		deleteImage: function (image, currentIndex) {
412
			// These are Gallery specific commands to be replaced
413
			// which should sit somewhere else
414
			if (!window.galleryFileAction) {
415
				delete Gallery.imageMap[image.path];
416
				delete Thumbnails.map[image.file];
417
				Gallery.albumMap[Gallery.currentAlbum].images.splice(currentIndex, 1);
418
				Gallery.view.init(Gallery.currentAlbum);
419
			}
420
		},
421
422
		/**
423
		 * Automatically fades the controls after 3 seconds
424
		 *
425
		 * @private
426
		 */
427
		_initControlsAutoFader: function () {
428
			var inactiveCallback = function () {
429
				this.container.addClass('inactive');
430
			}.bind(this);
431
			var inactiveTimeout = setTimeout(inactiveCallback, 3000);
432
433
			this.container.on('mousemove touchstart', function () {
434
				this.container.removeClass('inactive');
435
				clearTimeout(inactiveTimeout);
436
				inactiveTimeout = setTimeout(inactiveCallback, 3000);
437
			}.bind(this));
438
		},
439
440
		/**
441
		 * Simplest way to detect if image is transparent.
442
		 *
443
		 * That's very inaccurate since it doesn't include images which support transparency
444
		 *
445
		 * @param mimeType
446
		 * @returns {boolean}
447
		 * @private
448
		 */
449
		_isTransparent: function (mimeType) {
450
			return !(mimeType === 'image/jpeg'
451
				|| mimeType === 'image/x-dcraw'
452
				|| mimeType === 'application/font-sfnt'
453
				|| mimeType === 'application/x-font'
454
			);
455
		},
456
457
		/**
458
		 * Changes the browser Url, based on the current image
459
		 *
460
		 * @param {string} path
461
		 * @private
462
		 */
463
		_setUrl: function (path) {
464
			if (history && history.replaceState) {
0 ignored issues
show
Best Practice introduced by
If you intend to check if the variable history is declared in the current environment, consider using typeof history === "undefined" instead. This is safe if the variable is not actually declared.
Loading history...
465
				history.replaceState('', '', '#' + encodeURI(path));
466
			}
467
		},
468
469
		/**
470
		 * Hides the current image (before loading the next)
471
		 *
472
		 * @private
473
		 */
474
		_hideImage: function () {
475
			this.zoomablePreviewContainer.empty();
476
			this.controls.hideActionButtons();
477
		},
478
479
		/**
480
		 * Retrieves an SVG
481
		 *
482
		 * An SVG can't be simply attached to a src attribute like a bitmap image
483
		 *
484
		 * @param {string} source
485
		 *
486
		 * @returns {*}
487
		 * @private
488
		 */
489
		_getSVG: function (source) {
490
			var svgPreview = null;
491
			// DOMPurify only works with IE10+ and we load SVGs in the IMG tag
492
			if (window.btoa &&
493
				document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Image",
494
					"1.1")) {
495
				var xmlHttp = new XMLHttpRequest();
0 ignored issues
show
Bug introduced by
The variable XMLHttpRequest seems to be never declared. If this is a global, consider adding a /** global: XMLHttpRequest */ 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...
496
				xmlHttp.open("GET", source, false);
497
				xmlHttp.send(null);
498
				if (xmlHttp.status === 200) {
499
					var pureSvg = DOMPurify.sanitize(xmlHttp.responseText, {ADD_TAGS: ['filter']});
500
					// Remove XML comment garbage left in the purified data
501
					var badTag = pureSvg.indexOf(']&gt;');
502
					var fixedPureSvg = pureSvg.substring(badTag < 0 ? 0 : 5, pureSvg.length);
503
					svgPreview = "data:image/svg+xml;base64," + window.btoa(fixedPureSvg);
504
				}
505
			}
506
507
			return svgPreview;
508
		},
509
510
		/**
511
		 * Retrieves the slideshow's template
512
		 *
513
		 * @returns {*}
514
		 * @private
515
		 */
516
		_getSlideshowTemplate: function () {
517
			var defer = $.Deferred();
518
			if (!this.$slideshowTemplate) {
519
				var self = this;
520
				var url = OC.generateUrl('apps/gallery/slideshow', null);
521
				$.get(url, function (tmpl) {
522
						var template = $(tmpl);
523
						var tmplButton;
524
						var buttonsArray = [
525
							{
526
								el: '.next',
527
								trans: t('gallery', 'Next')
528
							},
529
							{
530
								el: '.play',
531
								trans: t('gallery', 'Play'),
532
								toolTip: true
533
							},
534
							{
535
								el: '.pause',
536
								trans: t('gallery', 'Pause'),
537
								toolTip: true
538
							},
539
							{
540
								el: '.previous',
541
								trans: t('gallery', 'Previous')
542
							},
543
							{
544
								el: '.exit',
545
								trans: t('gallery', 'Close'),
546
								toolTip: true
547
							},
548
							{
549
								el: '.downloadImage',
550
								trans: t('gallery', 'Download'),
551
								toolTip: true
552
							},
553
							{
554
								el: '.changeBackground',
555
								trans: t('gallery', 'Toggle background'),
556
								toolTip: true
557
							},
558
							{
559
								el: '.deleteImage',
560
								trans: t('gallery', 'Delete'),
561
								toolTip: true
562
							},
563
							{
564
								el: '.shareImage',
565
								trans: t('gallery', 'Share'),
566
								toolTip: true
567
							}
568
						];
569
						for (var i = 0; i < buttonsArray.length; i++) {
570
							var button = buttonsArray[i];
571
572
							tmplButton = template.find(button.el);
573
							tmplButton.val(button.trans);
574
							if (button.toolTip) {
575
								tmplButton.attr("title", button.trans);
576
							}
577
						}
578
						self.$slideshowTemplate = template;
579
						defer.resolve(self.$slideshowTemplate);
580
					})
581
					.fail(function () {
582
						defer.reject();
583
					});
584
			} else {
585
				defer.resolve(this.$slideshowTemplate);
586
			}
587
			return defer.promise();
588
		}
589
	};
590
591
	window.SlideShow = SlideShow;
592
})(jQuery, OC, OCA, t);
593