UNPKG

623 kBJavaScriptView Raw
1/* nanogallery2 - v3.0.3 - 2020-09-08 - https://nanogallery2.nanostudio.org */
2/*!
3 * @preserve nanogallery2 - javascript photo / video gallery and lightbox
4 * Homepage: http://nanogallery2.nanostudio.org
5 * Sources: https://github.com/nanostudio-org/nanogallery2
6 *
7 * License: GPLv3 and commercial licence
8 *
9 * Requirements:
10 * - jQuery (http://www.jquery.com) - version >= 1.7.1
11 *
12 * Embeded components:
13 * - shifty (https://github.com/jeremyckahn/shifty)
14 * - imagesloaded (https://github.com/desandro/imagesloaded)
15 * - hammer.js (http://hammerjs.github.io/)
16 * - screenfull.js (https://github.com/sindresorhus/screenfull.js)
17 * Tools:
18 * - webfont generated with http://fontello.com - mainly based on Font Awesome Copyright (C) 2012 by Dave Gandy (http://fontawesome.io/)
19 * - ICO online converter: https://iconverticons.com/online/
20 */
21
22// ###########################################
23// ##### nanogallery2 as a JQUERY PLUGIN #####
24// ###########################################
25
26
27// Expose plugin as an AMD module if AMD loader is present:
28(function (factory) {
29 "use strict";
30 if (typeof define === 'function' && define.amd) {
31 // AMD. Register as an anonymous module.
32 // define('nanogallery2', ['jquery'], factory);
33 define(['jquery'], factory);
34 } else if (typeof exports === 'object' && typeof require === 'function') {
35 // Browserify
36 factory(require('jquery'));
37 } else {
38 // Browser globals
39 factory(jQuery);
40 }
41}(function ($) {
42// ;(function ($) {
43 "use strict";
44
45 //##### TOOLS/HELPERS ####
46
47 // Convert color to RGB/RGBA
48 function ColorHelperToRGB( color ) {
49 var obj = document.getElementById('ngyColorHelperToRGB');
50 if (obj === null) {
51 obj = document.createElement('div');
52 obj.id = "ngyColorHelperToRGB";
53 obj.style.cssText = 'display: none; color:' + color + ';';
54 document.body.appendChild(obj);
55 }
56
57 var rgb = getComputedStyle(obj).color;
58
59 // to get HEX value:
60 // var rgb = getComputedStyle(obj).color.match(/\d+/g);
61 // var r = parseInt(rgb[0]).toString(16);
62 // var g = parseInt(rgb[1]).toString(16);
63 // var b = parseInt(rgb[2]).toString(16);
64 // var hex = '#' + r + g + b;
65
66 return rgb;
67 }
68
69
70 // ##### helper for color handling
71 // - normalise RGB/RGBA/HEX format
72 // - lighten/darken color
73 // Inspired by:
74 // https://stackoverflow.com/questions/5560248/programmatically-lighten-or-darken-a-hex-color-or-rgb-and-blend-colors
75 // http://www.pimptrizkit.com/?t=20%20Shades
76 function ShadeBlendConvert (p, from, to) {
77 var rgba='';
78 if( from.toUpperCase().substring(0,5) == 'RGBA(' ) {
79 rgba='a';
80 from='rgb('+from.substring(5);
81 }
82
83 if(typeof(p)!="number"||p<-1||p>1||typeof(from)!="string"||(from[0]!='r'&&from[0]!='#')||(typeof(to)!="string"&&typeof(to)!="undefined"))return null;
84 //if(!this.sbcRip)this.sbcRip=function(d){
85 function sbcRip(d){
86 var l=d.length,RGB=new Object();
87 if(l>9){
88 d=d.split(",");
89 if(d.length<3||d.length>4)return null;
90 RGB[0]=i(d[0].slice(4)),RGB[1]=i(d[1]),RGB[2]=i(d[2]),RGB[3]=d[3]?parseFloat(d[3]):-1;
91 }else{
92 if(l==8||l==6||l<4)return null;
93 if(l<6)d="#"+d[1]+d[1]+d[2]+d[2]+d[3]+d[3]+(l>4?d[4]+""+d[4]:"");
94 d=i(d.slice(1),16),RGB[0]=d>>16&255,RGB[1]=d>>8&255,RGB[2]=d&255,RGB[3]=l==9||l==5?r(((d>>24&255)/255)*10000)/10000:-1;
95 }
96 return RGB;
97 }
98 var i=parseInt,r=Math.round,h=from.length>9,h=typeof(to)=="string"?to.length>9?true:to=="c"?!h:false:h,b=p<0,p=b?p*-1:p,to=to&&to!="c"?to:b?"#000000":"#FFFFFF",f=sbcRip(from),t=sbcRip(to);
99 if(!f||!t)return null;
100 if(h)return "rgb"+rgba+"("+r((t[0]-f[0])*p+f[0])+","+r((t[1]-f[1])*p+f[1])+","+r((t[2]-f[2])*p+f[2])+(f[3]<0&&t[3]<0?")":","+(f[3]>-1&&t[3]>-1?r(((t[3]-f[3])*p+f[3])*10000)/10000:t[3]<0?f[3]:t[3])+")");
101 else return "#"+(0x100000000+(f[3]>-1&&t[3]>-1?r(((t[3]-f[3])*p+f[3])*255):t[3]>-1?r(t[3]*255):f[3]>-1?r(f[3]*255):255)*0x1000000+r((t[0]-f[0])*p+f[0])*0x10000+r((t[1]-f[1])*p+f[1])*0x100+r((t[2]-f[2])*p+f[2])).toString(16).slice(f[3]>-1||t[3]>-1?1:3);
102 }
103
104
105 // ##### clone a javascript object
106 function cloneJSObject( obj ) {
107 if (obj === null || typeof obj !== 'object') {
108 return obj;
109 }
110
111 var temp = obj.constructor(); // give temp the original obj's constructor
112 for (var key in obj) {
113 temp[key] = cloneJSObject(obj[key]);
114 }
115 return temp;
116 }
117
118 // get viewport coordinates and size
119 function getViewport() {
120 var $win = jQuery(window);
121 return {
122 l: $win.scrollLeft(),
123 t: $win.scrollTop(),
124 w: $win.width(),
125 h: $win.height()
126 }
127 }
128
129
130 // Check if element is in viewport
131 // avoid if possible (performance issue)
132 /*
133 function inViewport( $elt, threshold ) {
134 var wp = getViewport(),
135 eltOS = $elt.offset(),
136 th = $elt.outerHeight(true),
137 tw = $elt.outerWidth(true);
138 if( eltOS.top >= (wp.t - threshold)
139 && (eltOS.top + th) <= (wp.t + wp.h + threshold)
140 && eltOS.left >= (wp.l - threshold)
141 && (eltOS.left + tw) <= (wp.l + wp.w + threshold) ) {
142 return true;
143 }
144 else {
145 return false;
146 }
147 }
148 */
149
150
151 // Check if whole element is in ViewPort
152 // avoid if possible (performance issue)
153 function inViewportVert( $elt, threshold ) {
154 var wp = getViewport(),
155 eltOS = $elt.offset(),
156 th = $elt.outerHeight(true);
157 //var tw=$elt.outerWidth(true);
158
159 if( wp.t == 0 && (eltOS.top) <= (wp.t + wp.h ) ) { return true; }
160
161 if( eltOS.top >= wp.t && (eltOS.top + th) <= (wp.t + wp.h - threshold) ) {
162 return true;
163 }
164 else {
165 return false;
166 }
167 }
168 // Check if top of the element is in ViewPort
169 function topInViewportVert( $elt, threshold ) {
170 var wp = getViewport(),
171 eltOS = $elt.offset();
172
173 if( eltOS.top >= wp.t && eltOS.top <= (wp.t + wp.h - threshold) ) {
174 return true;
175 }
176 else {
177 return false;
178 }
179 }
180
181
182 // set z-index to display 2 elements on top of all others
183 // function set2ElementsOnTop( start, elt1, elt2 ) {
184 // var highest_index = 0;
185 // if( start=='' ) { start= '*'; }
186 // jQuery(start).each(function() {
187 // var cur = parseInt(jQuery(this).css('z-index'));
188 // highest_index = cur > highest_index ? cur : highest_index;
189 // });
190 // highest_index++;
191 // jQuery(elt2).css('z-index',highest_index+1);
192 // jQuery(elt1).css('z-index',highest_index);
193 // }
194
195 // set z-index to display element on top of all others
196 function setElementOnTop( start, elt ) {
197 var highest_index = 0;
198 if( start == '' ) { start = '*'; }
199 jQuery(start).each(function() {
200 var cur = parseInt(jQuery(this).css('z-index'));
201 highest_index = cur > highest_index ? cur : highest_index;
202 });
203 highest_index++;
204 jQuery(elt).css('z-index',highest_index);
205 }
206
207 // return the real type of the object
208 var toType = function( obj ) {
209 // by Angus Croll - http://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator/
210 return ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase()
211 };
212
213
214 $.nanogallery2 = function (elt, options) {
215 // To avoid scope issues, use '_this' instead of 'this'
216 // to reference this class from internal events and functions.
217 var _this = this;
218
219 // Access to jQuery and DOM versions of element
220 _this.$e = jQuery(elt);
221 _this.e = elt;
222
223 // Add a reverse reference to the DOM object
224 _this.$e.data('nanogallery2data', _this);
225
226 _this.init = function () {
227
228 // define these global objects only once per HTML page
229 if (typeof window.NGY2Item === 'undefined') {
230
231 window.NGY2Tools = (function () {
232
233 function NGY2Tools() {
234 var nextId = 1; // private static --> all instances
235 }
236
237 // check album name - albumList/blockList/allowList
238 NGY2Tools.FilterAlbumName = function( title, ID ) {
239 var s = title.toUpperCase();
240 if( this.albumList.length > 0 ) {
241 for( var j=0; j < this.albumList.length; j++) {
242 if( s === this.albumList[j].toUpperCase() || ID === this.albumList[j] ) {
243 return true;
244 }
245 }
246 }
247 else {
248 var found = false;
249 if( this.allowList !== null ) {
250 //allowList : authorize only album cointaining one of the specified keyword in the title
251 for( var j = 0; j < this.allowList.length; j++) {
252 if( s.indexOf(this.allowList[j]) !== -1 ) {
253 found = true;
254 }
255 }
256 if( !found ) { return false; }
257 }
258
259
260 if( this.blockList !== null ) {
261 //blockList : ignore album cointaining one of the specified keyword in the title
262 for( var j = 0; j < this.blockList.length; j++) {
263 if( s.indexOf(this.blockList[j]) !== -1 ) {
264 return false;
265 }
266 }
267 }
268 return true;
269 }
270 };
271
272
273 /** @function nanoAlert */
274 /* Display an alert message in a specific element */
275 NGY2Tools.NanoAlert = function(context, msg, verbose) {
276 NGY2Tools.NanoConsoleLog.call(context, msg);
277 if( context.$E.conConsole != null ) {
278 context.$E.conConsole.css({visibility: 'visible', minHeight: '100px'});
279 if( verbose == false ) {
280 context.$E.conConsole.append('<p>' + msg + '</p>');
281 }
282 else {
283 context.$E.conConsole.append('<p>nanogallery2: '+ msg + ' [' + context.baseEltID + ']</p>');
284 }
285 }
286 };
287
288
289 /** @function NanoConsoleLog */
290 /* write message to the browser console */
291 NGY2Tools.NanoConsoleLog = function(context, msg) {
292 if (window.console) { console.log('nanogallery2: ' + msg + ' [' + context.baseEltID + ']'); }
293 // debugger;
294 };
295
296
297 /** @function PreloaderDisplay() */
298 /* Display/hide preloader */
299 NGY2Tools.PreloaderDisplay = function(display) {
300 if( display === true ) {
301 // loading bar at the top of the gallery
302 this.$E.conLoadingB.removeClass('nanoGalleryLBarOff').addClass('nanoGalleryLBar');
303 // spinner over album thumbnail
304 if( this.GOM.albumIdxLoading != undefined && this.GOM.albumIdxLoading != -1 ) {
305 let item = this.I[this.GOM.albumIdxLoading];
306 item.$Elts['.nGY2TnImg'].addClass('nGY2GThumbnailLoaderDisplayed');
307 }
308 }
309 else {
310 // loading bar at the top of the gallery
311 this.$E.conLoadingB.removeClass('nanoGalleryLBar').addClass('nanoGalleryLBarOff');
312 // spinner over album thumbnail
313 if( this.GOM.albumIdxLoading != undefined && this.GOM.albumIdxLoading != -1 ) {
314 let item = this.I[this.GOM.albumIdxLoading];
315 item.$Elts['.nGY2TnImg'].removeClass('nGY2GThumbnailLoaderDisplayed');
316 }
317 }
318 };
319
320 // Scrambles the elements of an array
321 //+ Jonas Raoni Soares Silva
322 //@ http://jsfromhell.com/array/shuffle [v1.0]
323 NGY2Tools.AreaShuffle = function (o) {
324 for (var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
325 return o;
326 };
327
328 /** @function GetImageTitleFromURL() */
329 /* retrieve filemane */
330 NGY2Tools.GetImageTitleFromURL = function( imageURL ) {
331 if( this.O.thumbnailLabel.get('title') == '%filename' ) {
332 return (imageURL.split('/').pop()).replace('_',' ');
333 }
334
335 if( this.O.thumbnailLabel.get('title') == '%filenameNoExt' ) {
336 var s=imageURL.split('/').pop();
337 return (s.split('.').shift()).replace('_',' ');
338 }
339 // return imageURL;
340 return '';
341 };
342
343
344 /** @function AlbumPostProcess() */
345 /* post process one album based on plugin general parameters --> sorting/maxItems*/
346 NGY2Tools.AlbumPostProcess = function(albumID) {
347
348 // this function can probably be optimized....
349
350 var sortOrder = this.gallerySorting[this.GOM.curNavLevel];
351 var maxItems = this.galleryMaxItems[this.GOM.curNavLevel];
352
353 if( sortOrder != '' || maxItems > 0 ) {
354
355 // copy album's items to a new array
356 var currentAlbum = this.I.filter( function( obj ) {
357 return( obj.albumID == albumID && obj.kind != 'albumUp' );
358 });
359
360 // sorting options
361 switch( sortOrder ) {
362 case 'RANDOM':
363 currentAlbum = NGY2Tools.AreaShuffle(currentAlbum);
364 break;
365 case 'REVERSED':
366 currentAlbum = currentAlbum.reverse();
367 break;
368 case 'TITLEASC':
369 currentAlbum.sort(function (a, b) {
370 return( (a.title.toUpperCase() < b.title.toUpperCase()) ? -1 : ((a.title.toUpperCase() > b.title.toUpperCase()) ? 1 : 0) );
371 });
372 break;
373 case 'TITLEDESC':
374 currentAlbum.sort(function (a, b) {
375 return( (a.title.toUpperCase() > b.title.toUpperCase()) ? -1 : ((a.title.toUpperCase() < b.title.toUpperCase()) ? 1 : 0) );
376 });
377 break;
378 }
379
380 // max Items
381 if( maxItems > 0 && currentAlbum.length > maxItems ) {
382 currentAlbum.splice(maxItems - 1, currentAlbum.length-maxItems );
383 }
384
385 // remove the albums's items from the global items array
386 this.I.removeIf( function( obj ) {
387 return( obj.albumID == albumID && obj.kind != 'albumUp' );
388 });
389
390 // add the sorted items back to the album
391 this.I.push.apply(this.I, currentAlbum);
392
393 }
394 };
395
396 return NGY2Tools;
397 })();
398
399 // ====================
400 // ===== NGY2Item =====
401 // ====================
402 window.NGY2Item = (function() {
403 var nextId = 1; // private static --> all instances
404
405 // constructor
406 function NGY2Item( itemID ) {
407 //window.NGY2Item = function( itemID ) {
408 var ID = 0; // private
409
410 // public (this instance only)
411 if( itemID === undefined || itemID === null ) {
412 ID = nextId++;
413 }
414 else {
415 ID = itemID;
416 }
417 this.GetID = function () { return ID; };
418
419 // public
420 this.kind = ''; // 'image', 'album' or 'albumUp'
421 this.mediaKind = 'img'; // 'img', 'iframe', 'video'
422 this.mediaMarkup = '';
423 this.G = null; // pointer to global instance
424 this.title = ''; // image title
425 this.description = ''; // image description
426 this.albumID = 0; // ID of the parent album
427 this.src = ''; // full sized image URL
428 this.width = 0; // image width
429 this.height = 0; // image height
430 this.destinationURL = ''; // thumbnail destination URL --> open URL instead of displaying image
431 this.downloadURL = ''; // thumbnail download URL --> specify the image for download button
432 this.author = ''; // image/album author
433 this.left = 0; // store position to animate from old to new
434 this.top = 0;
435 this.width = 0; // store size to avoid setting width/height if not required
436 this.height = 0;
437 this.resizedContentWidth= 0; // store size of content (image) to avoid setting width/height if not required
438 this.resizedContentHeight= 0;
439 this.thumbs = { // URLs and sizes for user defined
440 url: { l1: { xs: '', sm:'', me: '', la: '', xl: '' }, lN: { xs: '', sm: '', me: '', la:'', xl: '' } },
441 width: { l1: { xs: 0, sm: 0, me: 0, la: 0 , xl: 0 }, lN: { xs: 0 , sm: 0, me: 0, la: 0, xl: 0 } },
442 height: { l1: { xs: 0, sm: 0, me: 0, la: 0 , xl: 0 }, lN: { xs: 0, sm: 0, me: 0, la: 0, xl: 0 } }
443 };
444 this.thumbnailImgRevealed = false; // thumbnail image already revealed
445 this.imageDominantColors = null; // base64 GIF
446 this.imageDominantColor = null; // HEX RGB
447 this.featured = false; // featured element
448 this.flickrThumbSizes = {}; // store URLs for all available thumbnail sizes (flickr)
449 this.picasaThumbs = null; // store URLs and sizes
450 this.hovered = false; // is the thumbnail currently hovered?
451 this.hoverInitDone = false;
452 this.contentIsLoaded = false; // album: are items already loaded?
453 this.contentLength = 0; // album: number of items (real number of items in memory)
454 this.numberItems = 0; // album: number of items (value returned by data source)
455 this.mediaNumber = 0; // media number in the album
456 this.mediaCounter = 0; // number of medias in an album
457 this.eltTransform = []; // store the CSS transformations
458 this.eltFilter = []; // store the CSS filters
459 this.eltEffect = []; // store data about hover effects animations
460 this.paginationLastPage = 0; // for albums
461 this.paginationLastWidth = 0; // for albums
462 this.customData = {};
463 this.selected = false;
464 this.imageWidth = 0; // image natural (real) width
465 this.imageHeight = 0; // image natural (real) height
466 this.$elt = null; // pointer to the corresponding DOM element
467 this.$Elts = []; // cached pointers to the thumbnail content -> to avoid jQuery().find()
468 this.tags = []; // list of tags of the current item
469 this.albumTagList = []; // list of all the tags of the items contained in the current album
470 this.albumTagListSel = []; // list of currently selected tags (only for albums)
471 this.exif = { exposure: '', flash: '', focallength: '', fstop: '', iso: '', model: '', time: '', location: ''};
472 this.deleted = false; // item is deleted -> do not display anymore
473 this.rotationAngle = 0; // image display rotation angle
474 }
475
476 // public static
477
478 NGY2Item.Get = function( instance, ID ) {
479 var l = instance.I.length;
480 for( var i = 0; i < l; i++ ) {
481 if( instance.I[i].GetID() == ID ) {
482 return instance.I[i];
483 }
484 }
485 return null;
486 };
487
488 NGY2Item.GetIdx = function( instance, ID ) {
489 var l = instance.I.length;
490 for( var i = 0; i < l; i++ ) {
491 if( instance.I[i].GetID() == ID ) {
492 return i;
493 }
494 }
495 return -1;
496 };
497
498 // create new item (image, album or albumUp)
499 NGY2Item.New = function( instance, title, description, ID, albumID, kind, tags ) {
500 var album = NGY2Item.Get( instance, albumID );
501
502 // title translation
503 if( instance.O.titleTranslationMap !== null ) {
504 let obj = instance.O.titleTranslationMap.find(o => o.title === title);
505 if( obj !== undefined ) {
506 title = obj.replace;
507 }
508 }
509
510
511 if( albumID != -1 && albumID != 0 && title !='image gallery by nanogallery2 [build]' ) {
512 if( instance.O.thumbnailLevelUp && album.getContentLength(false) == 0 && instance.O.album == '' ) {
513 // add navigation thumbnail (album up)
514 let item = new NGY2Item('0');
515 instance.I.push( item );
516 album.contentLength += 1;
517 item.title = 'UP';
518 item.albumID = albumID;
519 item.kind = 'albumUp';
520 item.G = instance;
521
522 jQuery.extend( true, item.thumbs.width, instance.tn.defaultSize.width);
523 jQuery.extend( true, item.thumbs.height, instance.tn.defaultSize.height);
524 }
525 }
526
527 var item = NGY2Item.Get(instance, ID);
528 if( item === null ){
529 // create a new item (otherwise, just update the existing one)
530 item = new NGY2Item(ID);
531 instance.I.push(item);
532 if( albumID != -1 && title !='image gallery by nanogallery2 [build]' ) {
533 album.contentLength+=1;
534 }
535 }
536 item.G = instance;
537
538 item.albumID = albumID;
539 item.kind = kind;
540 if( kind == 'image' ) {
541 album.mediaCounter += 1;
542 item.mediaNumber = album.mediaCounter;
543 }
544
545 // check keyword to find features images/albums
546 var kw = instance.O.thumbnailFeaturedKeyword;
547 if( kw != '' ) {
548 // check if item featured based on a keyword in the title or in the description
549 kw = kw.toUpperCase();
550 var p = title.toUpperCase().indexOf(kw);
551 if( p > -1) {
552 item.featured = true;
553 // remove keyword case unsensitive
554 title = title.substring(0, p) + title.substring(p+kw.length, title.length);
555 }
556 p = description.toUpperCase().indexOf(kw);
557 if( p > -1) {
558 item.featured=true;
559 // remove keyword case unsensitive
560 description=description.substring(0, p) + description.substring(p + kw.length, description.length);
561 }
562 }
563
564 // TAGS
565 // if( instance.galleryFilterTags.Get() != false ) {
566 // if( instance.galleryFilterTags.Get() == true ) {
567 // if( tags != '' && tags != undefined ) {
568 // use set tags
569 // item.setTags(tags.split(' '));
570 // }
571 // }
572 // else {
573 // extract tags starting with # (in title)
574 if( typeof instance.galleryFilterTags.Get() == 'string' ) {
575 switch( instance.galleryFilterTags.Get().toUpperCase() ) {
576 case 'TITLE': {
577 let re = /(?:^|\W)#(\w+)(?!\w)/g, match, matches = [];
578 // let tags = "";
579 while (match = re.exec(title)) {
580 matches.push(match[1].replace(/^\s*|\s*$/, '')); //trim trailing/leading whitespace
581 }
582 item.setTags(matches); //tags;
583 title = title.split('#').join(''); //replaceall
584 break;
585 }
586 case 'DESCRIPTION': {
587 let re = /(?:^|\W)#(\w+)(?!\w)/g, match2, matches2 = [];
588 // let tags = "";
589 while (match2 = re.exec(description)) {
590 matches2.push(match2[1].replace(/^\s*|\s*$/, '')); //trim trailing/leading whitespace
591 }
592 item.setTags(matches2); //tags;
593 description = description.split('#').join(''); //replaceall
594 break;
595 }
596 }
597 }
598 else {
599 if( tags != '' && tags != undefined ) {
600 // use set tags
601 item.setTags(tags.split(' '));
602 }
603 }
604 // }
605 // }
606
607 // set (maybe modified) fields title and description
608 item.title = escapeHtml(instance, title);
609 item.description = escapeHtml(instance, description);
610 return item;
611 };
612
613
614 // removes logically current item
615 NGY2Item.prototype.delete = function( ) {
616 this.deleted = true;
617
618 // update content length of parent album
619 this.G.I[NGY2Item.GetIdx(this.G, this.albumID)].contentLength--;
620 this.G.I[NGY2Item.GetIdx(this.G, this.albumID)].numberItems--;
621
622 // check if in GOM and removes it
623 var nbTn = this.G.GOM.items.length;
624 var ID = this.GetID();
625 var foundIdx = -1;
626 var foundGOMidx = -1;
627 for( var i = 0; i < nbTn ; i++ ) {
628 var curTn = this.G.GOM.items[i];
629 var item=this.G.I[curTn.thumbnailIdx];
630 if( item.GetID() == ID ) {
631 // FOUND
632 if( !curTn.neverDisplayed ) {
633 foundIdx = curTn.thumbnailIdx;
634 foundGOMidx = i;
635 }
636 }
637 else {
638 if( foundIdx != -1 ) {
639 if( !curTn.neverDisplayed ) {
640 // update index value
641 item.$getElt('.nGY2GThumbnail').data('index', i-1);
642 item.$getElt('.nGY2GThumbnailImg').data('index', i-1);
643 }
644 }
645 }
646 }
647 if( foundIdx != -1 ) {
648 // delete item in GOM and delete thumbnail
649 var G = this.G;
650 if( this.selected == true ) {
651 this.selected = false;
652 G.GOM.nbSelected--; // update the global counter
653 }
654 if( G.I[foundIdx].$elt !== null ) {
655 G.I[foundIdx].$elt.remove(); // delete thumbnail DOM object
656 }
657 G.GOM.items.splice(foundGOMidx, 1); // delete in GOM
658 if( G.GOM.lastDisplayedIdx != -1 ) {
659 G.GOM.lastDisplayedIdx -= 1;
660 }
661 }
662
663 // TODO: update medianumber of the other item in the same album
664 }
665
666 NGY2Item.prototype.addToGOM = function( ) {
667 // retrieve index
668 var ID = this.GetID();
669 var l = this.G.I.length;
670 for( var idx = 0; idx < l; idx++ ) {
671 var item = this.G.I[idx];
672 if( item.GetID() == ID ) {
673 var w = item.thumbImg().width;
674 var h = item.thumbImg().height;
675 // set default size if required
676 if( h == 0 ) {
677 h = this.G.tn.defaultSize.getHeight();
678 }
679 if( w == 0 ) {
680 w = this.G.tn.defaultSize.getWidth();
681 }
682 // add to GOM -> will be displayed on next refresh/resize
683 var tn = new this.G.GOM.GTn(idx, w, h);
684 this.G.GOM.items.push(tn);
685 break;
686 }
687 }
688
689 }
690
691
692 // function to avoid XSS issue - Cross Site Scripting
693 // original: https://github.com/janl/mustache.js/blob/master/mustache.js#L55
694 var entityMap = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;', '/': '&#x2F;', '`': '&#x60;', '=': '&#x3D;' };
695 function escapeHtml (instance, string) {
696 if( instance.O.allowHTMLinData == true ) {
697 return string;
698 }
699 else {
700 return String(string).replace(/[&<>"'`=\/]/g, function fromEntityMap (s) {
701 return entityMap[s];
702 });
703 }
704 }
705
706
707 NGY2Item.get_nextId = function () {
708 return nextId;
709 };
710
711 //=== public (shared across instances)
712
713 //--- cached sub elements
714 NGY2Item.prototype.$getElt = function( elt, forceRefresh ) {
715 if( this.$elt == null ) { return null; }
716 if( this.$Elts[elt] !== undefined && !forceRefresh == true ) {
717 return this.$Elts[elt];
718 }
719 else {
720 if( elt == '.nGY2GThumbnail' ) {
721 this.$Elts[elt]=this.$elt;
722 }
723 else {
724 this.$Elts[elt]=this.$elt.find(elt);
725 }
726 return this.$Elts[elt];
727 }
728 };
729
730 // remove one element (in DOM and in cache)
731 NGY2Item.prototype.removeElt = function( elt ) {
732 if( this.$elt == null ) { return; }
733 if( this.$Elts[elt] == undefined) { return; }
734 this.$Elts[elt].remove();
735 var index = this.$Elts.indexOf(elt);
736 this.$Elts.splice(index, 1);
737 };
738
739 //--- returns the album containing the item
740 NGY2Item.prototype.album = function() {
741 return this.G.I[NGY2Item.GetIdx(this.G, this.albumID)];
742 };
743
744 //--- viewer - transition can be disabled per media kind - returns true if current media supports transition (swipe)
745 NGY2Item.prototype.mediaTransition = function( ) {
746 if( this.G.O.viewerTransitionMediaKind.indexOf( this.mediaKind ) > -1 ) {
747 return true;
748 }
749 return false;
750 };
751
752 //--- set one image (url and size)
753 NGY2Item.prototype.imageSet = function( src, w, h ) {
754 this.src = src;
755 this.width = w;
756 this.height = h;
757 };
758
759 //--- set one thumbnail (url and size) - screenSize and level are optional
760 NGY2Item.prototype.thumbSet = function( src, w, h, screenSize, level ) {
761 var lst=['xs','sm','me','la','xl'];
762 if( typeof screenSize === 'undefined' || screenSize == '' || screenSize == null ) {
763 for( var i=0; i< lst.length; i++ ) {
764 if( typeof level === 'undefined' || level == '' ) {
765 this.thumbs.url.l1[lst[i]]=src;
766 this.thumbs.height.l1[lst[i]]=h;
767 this.thumbs.width.l1[lst[i]]=w;
768 this.thumbs.url.lN[lst[i]]=src;
769 this.thumbs.height.lN[lst[i]]=h;
770 this.thumbs.width.lN[lst[i]]=w;
771 }
772 else {
773 this.thumbs.url[level][lst[i]]=src;
774 this.thumbs.height[level][lst[i]]=h;
775 this.thumbs.width[level][lst[i]]=w;
776 }
777 }
778 }
779 else {
780 if( typeof level === 'undefined' || level == '' || level == null ) {
781 this.thumbs.url.l1[screenSize]=src;
782 this.thumbs.height.l1[screenSize]=h;
783 this.thumbs.width.l1[screenSize]=w;
784 this.thumbs.url.lN[screenSize]=src;
785 this.thumbs.height.lN[screenSize]=h;
786 this.thumbs.width.lN[screenSize]=w;
787 }
788 else {
789 this.thumbs.url[level][screenSize]=src;
790 this.thumbs.height[level][screenSize]=h;
791 this.thumbs.width[level][screenSize]=w;
792 }
793 }
794
795 for( var i=0; i< lst.length; i++ ) {
796 this.thumbs.height.l1[lst[i]]=h;
797 }
798 for( var i=0; i< lst.length; i++ ) {
799 if( this.G.tn.settings.height.lN[lst[i]] == this.G.tn.settings.getH() && this.G.tn.settings.width.l1[lst[i]] == this.G.tn.settings.getW() ) {
800 this.thumbs.height.lN[lst[i]]=h;
801 }
802 }
803 };
804
805 //--- set thumbnail image real height for current level/resolution, and for all others level/resolutions having the same settings
806 NGY2Item.prototype.thumbSetImgHeight = function( h ) {
807 var lst=['xs','sm','me','la','xl'];
808 for( var i=0; i< lst.length; i++ ) {
809 if( this.G.tn.settings.height.l1[lst[i]] == this.G.tn.settings.getH() && this.G.tn.settings.width.l1[lst[i]] == this.G.tn.settings.getW() ) {
810 this.thumbs.height.l1[lst[i]]=h;
811 }
812 }
813 for( var i=0; i< lst.length; i++ ) {
814 if( this.G.tn.settings.height.lN[lst[i]] == this.G.tn.settings.getH() && this.G.tn.settings.width.l1[lst[i]] == this.G.tn.settings.getW() ) {
815 this.thumbs.height.lN[lst[i]]=h;
816 }
817 }
818 };
819
820 //--- set thumbnail image real width for current level/resolution, and for all others level/resolutions having the same settings
821 NGY2Item.prototype.thumbSetImgWidth = function( w ) {
822 var lst=['xs','sm','me','la','xl'];
823 for( var i=0; i< lst.length; i++ ) {
824 if( this.G.tn.settings.height.l1[lst[i]] == this.G.tn.settings.getH() && this.G.tn.settings.width.l1[lst[i]] == this.G.tn.settings.getW() ) {
825 this.thumbs.width.l1[lst[i]]=w;
826 }
827 }
828 for( var i=0; i< lst.length; i++ ) {
829 if( this.G.tn.settings.height.lN[lst[i]] == this.G.tn.settings.getH() && this.G.tn.settings.width.l1[lst[i]] == this.G.tn.settings.getW() ) {
830 this.thumbs.width.lN[lst[i]]=w;
831 }
832 }
833 };
834
835 //--- Returns Thumbnail image (depending of the screen resolution)
836 NGY2Item.prototype.thumbImg = function () {
837 var tnImg = { src: '', width: 0, height: 0 };
838
839 if( this.title == 'image gallery by nanogallery2 [build]' ) {
840 tnImg.src = this.G.emptyGif;
841 tnImg.url = this.G.emptyGif;
842 return tnImg;
843 }
844 tnImg.src = this.thumbs.url[this.G.GOM.curNavLevel][this.G.GOM.curWidth];
845 tnImg.width = this.thumbs.width[this.G.GOM.curNavLevel][this.G.GOM.curWidth];
846 tnImg.height = this.thumbs.height[this.G.GOM.curNavLevel][this.G.GOM.curWidth];
847 return tnImg;
848 };
849
850 //--- Set tags to items and add these tags to the album
851 NGY2Item.prototype.setTags = function( tags ) {
852 if( tags.length > 0 ) {
853 this.tags = tags;
854 var lstTags = this.album().albumTagList;
855 for( var i = 0; i < tags.length; i++ ) {
856 var tfound = false;
857 for( var j = 0; j < lstTags.length; j++ ) {
858 if( tags[i].toUpperCase() == lstTags[j].toUpperCase() ) {
859 tfound = true;
860 }
861 }
862 if( tfound == false) {
863 this.album().albumTagList.push(tags[i])
864 // this.album().albumTagListSel.push(tags[i])
865 }
866 }
867 }
868 };
869
870 //--- check if 1 of current item's tags is selected (tag filter)
871 NGY2Item.prototype.checkTagFilter = function() {
872 if( this.G.galleryFilterTags.Get() != false && this.album().albumTagList.length > 0 ) {
873 if( this.G.O.thumbnailLevelUp && this.kind == 'albumUp' ) {
874 return true;
875 }
876 var found = false;
877 var lstTags = this.album().albumTagListSel;
878 if( lstTags.length == 0 ) {
879 // no tag is selected -> display all items
880 return true;
881 }
882 for( var i = 0; i < this.tags.length; i++ ) {
883 for( var j = 0; j < lstTags.length; j++ ) {
884 if( this.tags[i].toUpperCase() == lstTags[j].toUpperCase() ) {
885 found = true;
886 break;
887 }
888 }
889 }
890 return found;
891 }
892 else
893 return true;
894 };
895
896 //--- check if 1 of current item's tags is found using API search
897 NGY2Item.prototype.isSearchTagFound = function() {
898 if( this.G.GOM.albumSearchTags == '' ) { return true; }
899 if( this.G.O.thumbnailLevelUp && this.kind == 'albumUp' ) { return true; }
900
901 //var lstTags=this.album().albumTagListSel;
902 for( var i = 0; i < this.tags.length; i++ ) {
903 if( this.tags[i].toUpperCase().indexOf( this.G.GOM.albumSearchTags ) >= 0 ) {
904 return true;
905 }
906 }
907 return false;
908 };
909
910 //--- set the URL of the media to display in the viewer
911 //--- markup is defined for images
912 NGY2Item.prototype.setMediaURL = function( url, mediaKind ) {
913 this.src = url;
914 this.mediaKind = mediaKind;
915 if( mediaKind == 'img' ) {
916 this.mediaMarkup = '<img class="nGY2ViewerMedia" src="' + url + '" alt=" " itemprop="contentURL" draggable="false">';
917 }
918 };
919
920
921 //--- check if current item should be displayed
922 NGY2Item.prototype.isToDisplay = function( albumID ) {
923 return this.albumID == albumID && this.checkTagFilter() && this.isSearchFound() && this.isSearchTagFound() && this.deleted == false;
924 };
925
926
927
928 //--- returns the number of items of the current album
929 //--- count using tags filter
930 NGY2Item.prototype.getContentLength = function( filterTags ) {
931 if( filterTags == false || this.albumTagList.length == 0 || this.G.galleryFilterTags.Get() == false ) {
932 return this.contentLength;
933 }
934 else {
935 var l = this.G.I.length;
936 var cnt = 0;
937 var albumID = this.GetID();
938 for( var idx = 0; idx < l; idx++ ) {
939 var item = this.G.I[idx];
940 if( item.isToDisplay(albumID) ) {
941 cnt++;
942 }
943 }
944 return cnt;
945 }
946 };
947
948 NGY2Item.prototype.isSearchFound = function() {
949 if( this.G.GOM.albumSearch != '' ) {
950 if( this.title.toUpperCase().indexOf( this.G.GOM.albumSearch ) == -1 ) {
951 return false;
952 }
953 }
954 return true;
955 }
956
957
958 //--- for future use...
959 NGY2Item.prototype.responsiveURL = function () {
960 var url = '';
961 switch(this.G.O.kind) {
962 case '':
963 url = this.src;
964 break;
965 case 'flickr':
966 url = this.src;
967 break;
968 case 'picasa':
969 case 'google':
970 case 'google2':
971 default:
972 url = this.src;
973 break;
974 }
975 return url;
976 };
977
978
979 //--- Reveal the thumbnail image with animation on opacity
980 NGY2Item.prototype.ThumbnailImageReveal = function () {
981
982 if( this.thumbnailImgRevealed == false ) {
983 this.thumbnailImgRevealed = true;
984 new NGTweenable().tween({
985 from: { opacity: 0 },
986 to: { opacity: 1 },
987 attachment: { item: this },
988 delay: 30,
989 duration: 400,
990 easing: 'easeOutQuart',
991 step: function (state, att) {
992 var $e=att.item.$getElt('.nGY2TnImg');
993 if( $e != null ) {
994 $e.css( state );
995 }
996 }
997 });
998 }
999 };
1000
1001
1002 // In case of thumbnails with stacks - apply a percent to a value which include a unit
1003 function ValueApplyPercent( str, percent ) {
1004 str=String(str);
1005 if( str === '0' || percent == 1 ) { return str; }
1006 var n = Number(str.replace(/[a-zA-Z]/g, ''));
1007 var ar = str.match(/([^\-0-9\.]+)/g);
1008 var a = '';
1009 if( ar != null && ar.length > 0 ) {
1010 a = ar.join();
1011 }
1012
1013 if( isNaN(n) || n == 0 ) {
1014 return str;
1015 }
1016
1017 n = n * percent;
1018 return n + a;
1019 }
1020
1021 //--- 2D/3D CSS transform - apply the cached value to element
1022 NGY2Item.prototype.CSSTransformApply = function ( eltClass ) {
1023 var obj = this.eltTransform[eltClass];
1024
1025 if( eltClass == '.nGY2GThumbnail' ) {
1026 // thumbnail
1027 var nbStacks = obj.$elt.length-1;
1028 var pTranslateX = 1;
1029 var pTranslateY = 1;
1030 var pTranslateZ = 1;
1031 var pTranslate = 1;
1032 var pRotateX = 1;
1033 var pRotateY = 1;
1034 var pRotateZ = 1;
1035 var pRotate = 1;
1036 var pScale = 1;
1037 for( var n = nbStacks; n >= 0; n-- ) {
1038 // units must be given with
1039 var v = 'translateX(' + ValueApplyPercent(obj.translateX,pTranslateX) + ') translateY(' + ValueApplyPercent(obj.translateY,pTranslateY) + ') translateZ(' + ValueApplyPercent(obj.translateZ,pTranslateZ) + ') scale(' + ValueApplyPercent(obj.scale,pScale) + ') translate(' + ValueApplyPercent(obj.translate,pTranslate) + ')';
1040 if( !(this.G.IE <= 9) && !this.G.isGingerbread ) {
1041 v += ' rotateX(' + ValueApplyPercent(obj.rotateX,pRotateX) + ') rotateY(' + ValueApplyPercent(obj.rotateY,pRotateY) + ') rotateZ(' + ValueApplyPercent(obj.rotateZ,pRotateZ) + ') rotate(' + ValueApplyPercent(obj.rotate,pRotate) + ')';
1042 }
1043 else {
1044 v += ' rotate(' + ValueApplyPercent(obj.rotateZ,pRotateZ) + ')';
1045 }
1046 obj.$elt[n].style[this.G.CSStransformName] = v;
1047
1048 if( nbStacks > 0 ) {
1049 // apply a percent to the stack elements
1050 pTranslateX -= this.G.tn.opt.Get('stacksTranslateX');
1051 pTranslateY -= this.G.tn.opt.Get('stacksTranslateY');
1052 pTranslateZ -= this.G.tn.opt.Get('stacksTranslateZ');
1053 pRotateX -= this.G.tn.opt.Get('stacksRotateX');
1054 pRotateY -= this.G.tn.opt.Get('stacksRotateY');
1055 pRotateZ -= this.G.tn.opt.Get('stacksRotateZ');
1056 pScale -= this.G.tn.opt.Get('stacksScale');
1057 }
1058 }
1059 }
1060 else {
1061 // thumbnail sub element
1062 if( obj.$elt != null ) {
1063 for( var n = 0; n < obj.$elt.length; n++ ) {
1064 if( obj.$elt[n] != undefined ) {
1065 // units must be given with
1066 var v = 'translateX(' + obj.translateX + ') translateY(' + obj.translateY + ') translateZ(' + obj.translateZ + ') scale(' + obj.scale + ') translate(' + obj.translate + ')';
1067 if( !(this.G.IE <= 9) && !this.G.isGingerbread ) {
1068 v += ' rotateX(' + obj.rotateX + ') rotateY(' + obj.rotateY + ') rotateZ(' + obj.rotateZ + ') rotate(' + obj.rotate + ')';
1069 }
1070 else {
1071 v += ' rotate(' + obj.rotateZ + ')';
1072 }
1073 obj.$elt[n].style[this.G.CSStransformName] = v;
1074 }
1075 }
1076 }
1077 }
1078 };
1079
1080 //--- 2D/3D CSS transform - set a value in cache
1081 NGY2Item.prototype.CSSTransformSet = function ( eltClass, transform, value, forceRefresh ) {
1082 if( this.eltTransform[eltClass] == undefined ) {
1083 this.eltTransform[eltClass] = { translateX: 0, translateY: 0, translateZ: 0, rotateX: 0, rotateY: 0, rotateZ: 0, scale: 1, translate: '0px,0px', rotate: 0 };
1084 this.eltTransform[eltClass].$elt = this.$getElt(eltClass);
1085 }
1086 this.eltTransform[eltClass][transform] = value;
1087 if( forceRefresh === true ) {
1088 this.eltTransform[eltClass].$elt = this.$getElt(eltClass, true);
1089 }
1090 };
1091
1092 //--- CSS Filters - apply the cached value to element
1093 NGY2Item.prototype.CSSFilterApply = function ( eltClass ) {
1094 var obj = this.eltFilter[eltClass];
1095 var v = 'blur(' + obj.blur + ') brightness(' + obj.brightness + ') grayscale(' + obj.grayscale + ') sepia(' + obj.sepia + ') contrast(' + obj.contrast + ') opacity(' + obj.opacity + ') saturate(' + obj.saturate + ')';
1096 if( obj.$elt != null ) {
1097 for( var n = 0; n < obj.$elt.length; n++ ) {
1098 if( obj.$elt[n] != undefined ) {
1099 obj.$elt[n].style.WebkitFilter = v;
1100 obj.$elt[n].style.filter = v;
1101 }
1102 }
1103 }
1104 };
1105
1106 //--- CSS Filters - set a value in cache
1107 NGY2Item.prototype.CSSFilterSet = function ( eltClass, filter, value, forceRefresh ) {
1108 if( this.eltFilter[eltClass] == undefined ) {
1109 this.eltFilter[eltClass] = { blur: 0, brightness: '100%', grayscale: '0%', sepia: '0%', contrast: '100%', opacity: '100%', saturate: '100%' };
1110 this.eltFilter[eltClass].$elt = this.$getElt(eltClass);
1111 }
1112 this.eltFilter[eltClass][filter] = value;
1113 if( forceRefresh === true ) {
1114 this.eltTransform[eltClass].$elt = this.$getElt(eltClass, true);
1115 }
1116 };
1117
1118 //--- thumbnail hover animation
1119 NGY2Item.prototype.animate = function ( effect, delay, hoverIn ) {
1120 if( this.$getElt() == null ) { return; }
1121
1122 var context = {};
1123 context.G = this.G;
1124 context.item = this;
1125 context.effect = effect;
1126 context.hoverIn = hoverIn;
1127 context.cssKind = '';
1128 if( hoverIn ) {
1129 // HOVER IN
1130
1131 if( this.eltEffect[effect.element] == undefined ) {
1132 this.eltEffect[effect.element] = [];
1133 }
1134 if( this.eltEffect[effect.element][effect.type] == undefined ) {
1135 this.eltEffect[effect.element][effect.type] = { initialValue: 0, lastValue: 0 };
1136 }
1137 if( effect.firstKeyframe ) {
1138 // store initial and current value -> for use in the back animation
1139 this.eltEffect[effect.element][effect.type] = { initialValue: effect.from, lastValue: effect.from};
1140 }
1141
1142 context.animeFrom = effect.from;
1143 context.animeTo = effect.to;
1144 context.animeDuration = parseInt(effect.duration);
1145 context.animeDelay = 30 + parseInt(effect.delay + delay); // 30ms is a default delay to avoid conflict with other initializations
1146 context.animeEasing = effect.easing;
1147 }
1148 else {
1149 // HOVER OUT
1150 // if( effect.firstKeyframe ) {
1151 context.animeFrom = this.eltEffect[effect.element][effect.type].lastValue;
1152 context.animeTo = this.eltEffect[effect.element][effect.type].initialValue;
1153 // context.animeTo=effect.from;
1154 // }
1155 // else {
1156 // // context.animeFrom=effect.from;
1157 // context.animeFrom = this.eltEffect[effect.element][effect.type].lastValue;
1158 // context.animeTo = this.eltEffect[effect.element][effect.type].initialValue;
1159 // //context.animeTo=effect.to;
1160
1161 // }
1162
1163 context.animeDuration = parseInt(effect.durationBack);
1164 context.animeDelay = 30 + parseInt(effect.delayBack + delay); // 30ms is a default delay to avoid conflict with other initializations
1165 context.animeEasing = effect.easingBack;
1166 }
1167
1168
1169 // detect if animation on CSS transform
1170 var transform=['translateX', 'translateY', 'translateZ', 'scale', 'rotateX', 'rotateY', 'rotateZ'];
1171 for( var i = 0; i < transform.length; i++ ) {
1172 if( effect.type == transform[i] ) {
1173 context.cssKind = 'transform';
1174 break;
1175 }
1176 }
1177
1178 // detect if animation on CSS filter
1179 var filter=['blur', 'brightness', 'grayscale', 'sepia', 'contrast', 'opacity', 'saturate'];
1180 for( var i = 0; i < filter.length; i++ ) {
1181 if( effect.type == filter[i] ) {
1182 context.cssKind = 'filter';
1183 break;
1184 }
1185 }
1186 // handle some special cases
1187 if( hoverIn && effect.element == '.nGY2GThumbnail' && ( effect.type == 'scale' || effect.type == 'rotateX') ) {
1188 this.G.GOM.lastZIndex++;
1189 this.$getElt(effect.element).css('z-index', this.G.GOM.lastZIndex);
1190 // setElementOnTop(this.G.$E.base, this.$getElt(effect.element) );
1191 }
1192
1193 // animation
1194 var tweenable = new NGTweenable();
1195 context.tweenable=tweenable;
1196 tweenable.tween({
1197 attachment: context,
1198 from: { 'v': context.animeFrom },
1199 to: { 'v': context.animeTo },
1200 duration: context.animeDuration, //parseInt(effect.duration),
1201 delay: context.animeDelay, //parseInt(effect.delay),
1202 easing: context.animeEasing, //'easeOutQuart',
1203
1204 step: function (state, att) {
1205 if( att.item.$getElt() == null ) {
1206 // the thumbnail may be destroyed since the start of the animation
1207 att.tweenable.stop(false);
1208 // att.tweenable.dispose();
1209 return;
1210 }
1211 if( att.hoverIn && !att.item.hovered ) {
1212 // thumbnail no more hovered
1213 att.tweenable.stop(false);
1214 // att.tweenable.dispose();
1215 return;
1216 }
1217
1218 if( att.G.VOM.viewerDisplayed ) {
1219 att.tweenable.stop(false);
1220 // att.tweenable.dispose();
1221 return;
1222 }
1223
1224 // test if in delay phase
1225 if( state.v == att.animeFrom ) { return; }
1226
1227 switch( att.cssKind ) {
1228 case 'transform':
1229 // window.ng_draf( function() {
1230 att.item.CSSTransformSet(att.effect.element, att.effect.type, state.v);
1231 att.item.CSSTransformApply( att.effect.element );
1232 // });
1233 break;
1234 case 'filter':
1235 // window.ng_draf( function() {
1236 att.item.CSSFilterSet(att.effect.element, att.effect.type, state.v);
1237 att.item.CSSFilterApply( att.effect.element );
1238 // });
1239 break;
1240 default:
1241 var v=state.v;
1242 if( state.v.substring(0,4) == 'rgb(' || state.v.substring(0,5) == 'rgba(' ) {
1243 // to remove values after the dot (not supported by RGB/RGBA)
1244 // v=ngtinycolor(state.v).toRgbString();
1245 v = ShadeBlendConvert(0, v);
1246 }
1247 // window.ng_draf( function() {
1248 att.item.$getElt( att.effect.element ).css( att.effect.type, v );
1249 // });
1250 break;
1251 }
1252 if( hoverIn ) {
1253 // store value for back animation
1254 att.item.eltEffect[att.effect.element][att.effect.type].lastValue = state.v;
1255 }
1256 },
1257
1258 finish: function (state, att) {
1259 if( hoverIn ) {
1260 // store value for back animation
1261 att.item.eltEffect[att.effect.element][att.effect.type].lastValue = state.v;
1262 }
1263
1264 if( att.item.$getElt() == null ) {
1265 // the thumbnail may be destroyed since the start of the animation
1266 return;
1267 }
1268 if( att.hoverIn && !att.item.hovered ) {
1269 // thumbnail no more hovered
1270 return;
1271 }
1272
1273 if( att.G.VOM.viewerDisplayed ) {
1274 return;
1275 }
1276
1277 // window.ng_draf( function() {
1278 switch( att.cssKind ) {
1279 case 'transform':
1280 att.item.CSSTransformSet(att.effect.element, att.effect.type, att.animeTo);
1281 att.item.CSSTransformApply(att.effect.element);
1282 break;
1283 case 'filter':
1284 att.item.CSSFilterSet(att.effect.element, att.effect.type, att.animeTo);
1285 att.item.CSSFilterApply(att.effect.element);
1286 break;
1287 default:
1288 att.item.$getElt(att.effect.element).css(att.effect.type, att.animeTo);
1289 break;
1290 }
1291 // });
1292 }
1293 });
1294 };
1295
1296 return NGY2Item;
1297 })();
1298
1299 }
1300
1301 _this.options = jQuery.extend(true, {}, jQuery.nanogallery2.defaultOptions, options);
1302 // Initialization code
1303 _this.nG2 = null;
1304 _this.nG2 = new nanoGALLERY2();
1305 _this.nG2.initiateGallery2(_this.e, _this.options );
1306
1307 };
1308
1309 // PUBLIC EXPOSED METHODS
1310 _this.test = function() {
1311 //alert('test');
1312 // console.dir(_this.nG.G.I.length);
1313 // console.dir(_this.nG);
1314 // debugger;
1315 //privateTest();
1316 }
1317
1318
1319 // Run initializer
1320 _this.init();
1321 };
1322
1323 jQuery.nanogallery2.defaultOptions = {
1324 kind : '',
1325 userID : '',
1326 photoset : '',
1327 album: '',
1328 blockList : 'scrapbook|profil|auto backup',
1329 tagBlockList: '',
1330 allowList : '',
1331 albumList : '',
1332 albumList2 : null,
1333 RTL : false,
1334 flickrSkipOriginal : true,
1335 flickrAPIKey: '',
1336 breadcrumbAutoHideTopLevel : true,
1337 displayBreadcrumb : true,
1338 breadcrumbOnlyCurrentLevel : true,
1339 breadcrumbHideIcons : true,
1340 theme : 'nGY2',
1341 galleryTheme : 'dark',
1342 viewerTheme : 'dark',
1343 items : null,
1344 itemsBaseURL : '',
1345 thumbnailSelectable : false,
1346 dataProvider: '',
1347 allowHTMLinData: false,
1348 locationHash : true,
1349 slideshowDelay : 3000,
1350 slideshowAutoStart : false,
1351
1352 debugMode: false,
1353
1354 titleTranslationMap: null,
1355 galleryDisplayMoreStep : 2,
1356 galleryDisplayMode : 'fullContent',
1357 galleryL1DisplayMode : null,
1358 galleryPaginationMode : 'rectangles', // 'dots', 'rectangles', 'numbers'
1359 galleryPaginationTopButtons : true,
1360 galleryMaxRows : 2,
1361 galleryL1MaxRows : null,
1362 galleryLastRowFull: false,
1363 galleryL1LastRowFull: null,
1364 galleryLayoutEngine : 'default',
1365 paginationSwipe: true,
1366 paginationVisiblePages : 10,
1367 galleryFilterTags : false, // possible values: false, true, 'title', 'description'
1368 galleryL1FilterTags : null, // possible values: false, true, 'title', 'description'
1369 galleryFilterTagsMode : 'single',
1370 galleryL1FilterTagsMode : null,
1371 galleryMaxItems : 0, // maximum number of items per album --> only flickr, google2, nano_photos_provider2
1372 galleryL1MaxItems : null, // maximum number of items per gallery page --> only flickr, google2, nano_photos_provider2
1373 gallerySorting : '',
1374 galleryL1Sorting : null,
1375 galleryDisplayTransition : 'none',
1376 galleryL1DisplayTransition : null,
1377 galleryDisplayTransitionDuration : 1000,
1378 galleryL1DisplayTransitionDuration : null,
1379 galleryResizeAnimation : false,
1380 galleryRenderDelay : 10,
1381
1382 thumbnailCrop : true,
1383 thumbnailL1Crop : null,
1384 thumbnailCropScaleFactor : 1.5,
1385 thumbnailLevelUp : false,
1386 thumbnailAlignment : 'fillWidth',
1387 thumbnailWidth : 300,
1388 thumbnailL1Width : null,
1389 thumbnailHeight : 200,
1390 thumbnailL1Height : null,
1391 thumbnailBaseGridHeight : 0,
1392 thumbnailL1BaseGridHeight : null,
1393 thumbnailGutterWidth : 2,
1394 thumbnailL1GutterWidth : null,
1395 thumbnailGutterHeight : 2,
1396 thumbnailL1GutterHeight : null,
1397 thumbnailBorderVertical : 2,
1398 thumbnailL1BorderVertical : null,
1399 thumbnailBorderHorizontal : 2,
1400 thumbnailL1BorderHorizontal : null,
1401 thumbnailFeaturedKeyword : '*featured',
1402 thumbnailAlbumDisplayImage : false,
1403 thumbnailHoverEffect2 : 'toolsAppear',
1404 thumbnailBuildInit2 : '',
1405 thumbnailStacks : 0,
1406 thumbnailL1Stacks : null,
1407 thumbnailStacksTranslateX : 0,
1408 thumbnailL1StacksTranslateX : null,
1409 thumbnailStacksTranslateY : 0,
1410 thumbnailL1StacksTranslateY : null,
1411 thumbnailStacksTranslateZ : 0,
1412 thumbnailL1StacksTranslateZ : null,
1413 thumbnailStacksRotateX : 0,
1414 thumbnailL1StacksRotateX : null,
1415 thumbnailStacksRotateY : 0,
1416 thumbnailL1StacksRotateY : null,
1417 thumbnailStacksRotateZ : 0,
1418 thumbnailL1StacksRotateZ : null,
1419 thumbnailStacksScale : 0,
1420 thumbnailL1StacksScale : null,
1421 thumbnailDisplayOutsideScreen: true,
1422 thumbnailWaitImageLoaded: true,
1423 thumbnailSliderDelay: 2000,
1424 galleryBuildInit2 : '',
1425 portable : false,
1426 eventsDebounceDelay: 10,
1427
1428 touchAnimation : false,
1429 touchAnimationL1 : undefined,
1430 touchAutoOpenDelay : 0,
1431
1432 thumbnailLabel : {
1433 position : 'overImage',
1434 align: 'center',
1435 valign: 'bottom',
1436 display : true,
1437 displayDescription : false,
1438 titleMaxLength : 0,
1439 titleMultiLine : false,
1440 descriptionMaxLength : 0,
1441 descriptionMultiLine : false,
1442 hideIcons : true,
1443 title : ''
1444 },
1445
1446 thumbnailToolbarImage : { topLeft: 'select', topRight : 'featured' },
1447 thumbnailToolbarAlbum : { topLeft: 'select', topRight : 'counter' },
1448 thumbnailDisplayOrder : '',
1449 thumbnailL1DisplayOrder : null,
1450 thumbnailDisplayInterval : 15,
1451 thumbnailL1DisplayInterval : null,
1452 thumbnailDisplayTransition : 'fadeIn',
1453 thumbnailL1DisplayTransition : null,
1454 thumbnailDisplayTransitionEasing : 'easeOutQuart',
1455 thumbnailL1DisplayTransitionEasing : null,
1456 thumbnailDisplayTransitionDuration: 240,
1457 thumbnailL1DisplayTransitionDuration: null,
1458 thumbnailOpenInLightox : true,
1459 thumbnailOpenOriginal : false,
1460
1461 lightboxStandalone: false,
1462 viewer : 'internal',
1463 viewerFullscreen: false,
1464 imageTransition : 'swipe2',
1465 viewerTransitionMediaKind : 'img',
1466 viewerZoom : true,
1467 viewerImageDisplay : '',
1468 openOnStart : '',
1469 viewerHideToolsDelay : 4000,
1470 viewerToolbar : {
1471 display : false,
1472 position : 'bottom',
1473 fullWidth : false,
1474 align : 'center',
1475 autoMinimize : 0,
1476 standard : 'minimizeButton,label',
1477 minimized : 'minimizeButton,label,infoButton,shareButton,fullscreenButton'
1478 },
1479 viewerTools : {
1480 topLeft : 'pageCounter,playPauseButton',
1481 topRight : 'rotateLeft,rotateRight,fullscreenButton,closeButton'
1482 },
1483 viewerGallery: 'bottomOverMedia',
1484 viewerGalleryTWidth: 40,
1485 viewerGalleryTHeight: 40,
1486
1487 breakpointSizeSM : 480,
1488 breakpointSizeME : 992,
1489 breakpointSizeLA : 1200,
1490 breakpointSizeXL : 1800,
1491
1492 fnThumbnailInit : null,
1493 fnThumbnailHoverInit : null,
1494 fnThumbnailHover : null,
1495 fnThumbnailHoverOut : null,
1496 fnThumbnailDisplayEffect : null,
1497 fnViewerInfo : null,
1498 fnImgToolbarCustInit : null,
1499 fnImgToolbarCustDisplay : null,
1500 fnImgToolbarCustClick : null,
1501 fnProcessData : null,
1502 fnThumbnailSelection : null,
1503 fnGalleryRenderStart : null,
1504 fnGalleryRenderEnd : null,
1505 fnGalleryObjectModelBuilt : null,
1506 fnGalleryLayoutApplied : null,
1507 fnThumbnailClicked : null,
1508 fnShoppingCartUpdated : null,
1509 fnThumbnailToolCustAction : null,
1510 fnThumbnailOpen : null,
1511 fnImgDisplayed : null,
1512 fnPopupMediaInfo : null,
1513
1514 i18n : {
1515 'breadcrumbHome' : 'Galleries', 'breadcrumbHome_FR' : 'Galeries',
1516 'thumbnailImageTitle' : '', 'thumbnailAlbumTitle' : '',
1517 'thumbnailImageDescription' : '', 'thumbnailAlbumDescription' : '',
1518 'infoBoxPhoto' : 'Photo', 'infoBoxDate' : 'Date', 'infoBoxAlbum' : 'Album', 'infoBoxDimensions' : 'Dimensions', 'infoBoxFilename' : 'Filename', 'infoBoxFileSize' : 'File size', 'infoBoxCamera' : 'Camera', 'infoBoxFocalLength' : 'Focal length', 'infoBoxExposure' : 'Exposure', 'infoBoxFNumber' : 'F Number', 'infoBoxISO' : 'ISO', 'infoBoxMake' : 'Make', 'infoBoxFlash' : 'Flash', 'infoBoxViews' : 'Views', 'infoBoxComments' : 'Comments'
1519 },
1520 icons : {
1521 // example for font awesome: <i style="color:#eee;" class="fa fa-search-plus"></i>
1522 thumbnailAlbum: '<i class="nGY2Icon-folder-empty"></i>',
1523 thumbnailImage: '<i class="nGY2Icon-picture"></i>',
1524 breadcrumbAlbum: '<i class="nGY2Icon-folder-empty"></i>',
1525 breadcrumbHome: '<i class="nGY2Icon-home"></i>',
1526 breadcrumbSeparator: '<i class="nGY2Icon-left-open"></i>',
1527 breadcrumbSeparatorRtl: '<i class="nGY2Icon-right-open"></i>',
1528 navigationFilterSelected: '<i style="color:#fff;" class="nGY2Icon-ok"></i>',
1529 navigationFilterUnselected: '<i style="color:#ddd;opacity:0.3;" class="nGY2Icon-circle-empty"></i>',
1530 navigationFilterSelectedAll: '<i class="nGY2Icon-ccw"></i>',
1531 navigationPaginationPrevious: '<i class="nGY2Icon-ngy2_chevron-left"></i>',
1532 navigationPaginationNext: '<i class="nGY2Icon-ngy2_chevron-right"></i>',
1533 thumbnailSelected: '<i style="color:#bff;" class="nGY2Icon-ok-circled"></i>',
1534 thumbnailUnselected: '<i style="color:#bff;" class="nGY2Icon-circle-empty"></i>',
1535 thumbnailFeatured: '<i style="color:#dd5;" class="nGY2Icon-star"></i>',
1536 thumbnailCounter: '<i class="nGY2Icon-picture"></i>',
1537 thumbnailShare: '<i class="nGY2Icon-ngy2_share2"></i>',
1538 thumbnailDownload: '<i class="nGY2Icon-ngy2_download2"></i>',
1539 thumbnailInfo: '<i class="nGY2Icon-ngy2_info2"></i>',
1540 thumbnailShoppingcart: '<i class="nGY2Icon-basket"></i>',
1541 thumbnailDisplay: '<i class="nGY2Icon-resize-full"></i>',
1542 thumbnailCustomTool1: 'T1',
1543 thumbnailCustomTool2: 'T2',
1544 thumbnailCustomTool3: 'T3',
1545 thumbnailCustomTool4: 'T4',
1546 thumbnailCustomTool5: 'T5',
1547 thumbnailCustomTool6: 'T6',
1548 thumbnailCustomTool7: 'T7',
1549 thumbnailCustomTool8: 'T8',
1550 thumbnailCustomTool9: 'T9',
1551 thumbnailCustomTool10: 'T10',
1552 thumbnailAlbumUp: '<i style="font-size: 3em;" class="nGY2Icon-ngy2_chevron_up2"></i>',
1553 paginationNext: '<i class="nGY2Icon-right-open"></i>',
1554 paginationPrevious: '<i class="nGY2Icon-left-open"></i>',
1555 galleryMoreButton: '<i class="nGY2Icon-picture"></i> &nbsp; <i class="nGY2Icon-right-open"></i>',
1556 buttonClose: '<i class="nGY2Icon-ngy2_close2"></i>',
1557 viewerPrevious: '<i class="nGY2Icon-ngy2_chevron-left"></i>',
1558 viewerNext: '<i class="nGY2Icon-ngy2_chevron-right"></i>',
1559 viewerImgPrevious: '<i class="nGY2Icon-ngy2_chevron_left3"></i>',
1560 viewerImgNext: '<i class="nGY2Icon-ngy2_chevron_right3"></i>',
1561 viewerDownload: '<i class="nGY2Icon-ngy2_download2"></i>',
1562 viewerToolbarMin: '<i class="nGY2Icon-ellipsis-vert"></i>',
1563 viewerToolbarStd: '<i class="nGY2Icon-menu"></i>',
1564 viewerPlay: '<i class="nGY2Icon-play"></i>',
1565 viewerPause: '<i class="nGY2Icon-pause"></i>',
1566 viewerFullscreenOn: '<i class="nGY2Icon-resize-full"></i>',
1567 viewerFullscreenOff: '<i class="nGY2Icon-resize-small"></i>',
1568 viewerZoomIn: '<i class="nGY2Icon-ngy2_zoom_in2"></i>',
1569 viewerZoomOut: '<i class="nGY2Icon-ngy2_zoom_out2"></i>',
1570 viewerLinkOriginal: '<i class="nGY2Icon-ngy2_external2"></i>',
1571 viewerInfo: '<i class="nGY2Icon-ngy2_info2"></i>',
1572 viewerShare: '<i class="nGY2Icon-ngy2_share2"></i>',
1573 viewerRotateLeft: '<i class="nGY2Icon-ccw"></i>',
1574 viewerRotateRight: '<i class="nGY2Icon-cw"></i>',
1575 viewerShoppingcart: '<i class="nGY2Icon-basket"></i>',
1576 user: '<i class="nGY2Icon-user"></i>',
1577 location: '<i class="nGY2Icon-location"></i>',
1578 picture: '<i class="nGY2Icon-picture"></i>',
1579 config: '<i class="nGY2Icon-wrench"></i>',
1580 shareFacebook: '<i style="color:#3b5998;" class="nGY2Icon-facebook-squared"></i>',
1581 shareTwitter: '<i style="color:#00aced;" class="nGY2Icon-twitter-squared"></i>',
1582 // shareGooglePlus: '<i style="color:#dd4b39;" class="nGY2Icon-gplus-squared"></i>',
1583 shareTumblr: '<i style="color:#32506d;" class="nGY2Icon-tumblr-squared"></i>',
1584 sharePinterest: '<i style="color:#cb2027;" class="nGY2Icon-pinterest-squared"></i>',
1585 shareVK: '<i style="color:#3b5998;" class="nGY2Icon-vkontakte"></i>',
1586 shareMail: '<i style="color:#555;" class="nGY2Icon-mail-alt"></i>',
1587 viewerCustomTool1: 'T1',
1588 viewerCustomTool2: 'T2',
1589 viewerCustomTool3: 'T3',
1590 viewerCustomTool4: 'T4',
1591 viewerCustomTool5: 'T5',
1592 viewerCustomTool6: 'T6',
1593 viewerCustomTool7: 'T7',
1594 viewerCustomTool8: 'T8',
1595 viewerCustomTool9: 'T9',
1596 viewerCustomTool10: 'T10'
1597 }
1598 };
1599
1600 jQuery.fn.nanogallery2 = function (args, option, value) {
1601
1602 if( typeof jQuery(this).data('nanogallery2data') === 'undefined'){
1603 if( args == 'destroy' ) {
1604 // command to destroy but no instance yet --> exit
1605 return;
1606 }
1607
1608 return this.each( function(){
1609 (new jQuery.nanogallery2(this, args));
1610 });
1611 }
1612 else {
1613 // no options -->
1614 // This function breaks the chain, but provides some API methods
1615 var nG2 = $(this).data('nanogallery2data').nG2;
1616
1617 // Lightbox standalone
1618 // (Another click on an already opened media)
1619 if( args !== undefined && args.lightboxStandalone === true ) {
1620 // items exist already (G.I is populated) -> just open the lightbox again
1621 nG2.LightboxReOpen();
1622 return;
1623 }
1624
1625 switch(args){
1626 case 'displayItem':
1627 nG2.DisplayItem(option);
1628 break;
1629
1630 case 'search':
1631 return( nG2.Search(option));
1632 break;
1633
1634 case 'search2':
1635 return nG2.Search2(option, value);
1636 break;
1637
1638 case 'search2Execute':
1639 return nG2.Search2Execute();
1640 break;
1641
1642 case 'refresh':
1643 nG2.Refresh();
1644 break;
1645
1646 case 'resize':
1647 nG2.Resize();
1648 break;
1649
1650 case 'instance':
1651 return nG2;
1652 break;
1653
1654 case 'data':
1655 nG2.data= {
1656 items: nG2.I,
1657 gallery: nG2.GOM,
1658 lightbox: nG2.VOM,
1659 shoppingcart: nG2.shoppingCart
1660 };
1661 return nG2.data;
1662 break;
1663
1664 case 'reload':
1665 nG2.ReloadAlbum();
1666 return $(this);
1667 break;
1668
1669 case 'itemsSelectedGet':
1670 return nG2.ItemsSelectedGet();
1671 break;
1672
1673 case 'itemsSetSelectedValue':
1674 nG2.ItemsSetSelectedValue(option, value);
1675 break;
1676
1677 case 'option':
1678 if(typeof value === 'undefined'){
1679 return nG2.Get(option);
1680 }else{
1681 nG2.Set(option,value);
1682 if( option == 'demoViewportWidth' ) {
1683 // force resize event -> for demo purposes
1684 $(window).trigger('resize');
1685 }
1686 }
1687 break;
1688
1689 case 'destroy':
1690 nG2.Destroy();
1691 $(this).removeData('nanogallery2data');
1692 break;
1693
1694 case 'shoppingCartGet':
1695 // returns the content of the shoppingcart
1696 return nG2.shoppingCart;
1697 break;
1698
1699 case 'shoppingCartUpdate':
1700 // parameters :
1701 // - option = item's ID
1702 // - value = new quantity
1703
1704 if( typeof value === 'undefined' || typeof option === 'undefined' ){
1705 return false;
1706 }
1707
1708 var item_ID = option;
1709 var new_qty = value;
1710
1711 for( var i=0; i < nG2.shoppingCart.length; i++) {
1712 if( nG2.shoppingCart[i].ID == item_ID ) {
1713
1714 // updates counter
1715 nG2.shoppingCart[i].qty = new_qty;
1716
1717 let item = nG2.I[nG2.shoppingCart[i].idx];
1718
1719 // updates thumbnail
1720 nG2.ThumbnailToolbarOneCartUpdate( item );
1721
1722 if( new_qty == 0 ) {
1723 // removes item from shoppingcart
1724 nG2.shoppingCart.splice(i, 1);
1725 }
1726
1727 var fu = nG2.O.fnShoppingCartUpdated;
1728 if( fu !== null ) {
1729 typeof fu == 'function' ? fu(nG2.shoppingCart, item, 'api') : window[fu](nG2.shoppingCart, item, 'api');
1730 }
1731
1732 break;
1733 }
1734 }
1735
1736 return nG2.shoppingCart;
1737 break;
1738
1739 case 'shoppingCartRemove':
1740 // parameters :
1741 // - option = item's ID
1742 if( typeof option === 'undefined' ){
1743 return false;
1744 }
1745 var ID = option;
1746 for( var i=0; i < nG2.shoppingCart.length; i++) {
1747 if( nG2.shoppingCart[i].ID == ID ) {
1748
1749 var item = nG2.I[nG2.shoppingCart[i].idx];
1750
1751 // updates thumbnail
1752 nG2.shoppingCart[i].qty = 0;
1753 nG2.ThumbnailToolbarOneCartUpdate( item );
1754
1755 // removes item from shoppingcart
1756 nG2.shoppingCart.splice(i, 1);
1757
1758
1759 var fu = nG2.O.fnShoppingCartUpdated;
1760 if( fu !== null ) {
1761 typeof fu == 'function' ? fu(nG2.shoppingCart, item, 'api') : window[fu](nG2.shoppingCart, item, 'api');
1762 }
1763
1764 break;
1765 }
1766 }
1767
1768 return nG2.shoppingCart;
1769 break;
1770
1771 case 'closeViewer':
1772 nG2.CloseViewer();
1773 break;
1774 case 'minimizeToolbar':
1775 nG2.MinimizeToolbar();
1776 break;
1777 case 'maximizeToolbar':
1778 nG2.MaximizeToolbar();
1779 break;
1780 case 'paginationPreviousPage':
1781 nG2.PaginationPreviousPage();
1782 break;
1783 case 'paginationNextPage':
1784 nG2.paginationNextPage();
1785 break;
1786 case 'paginationGotoPage':
1787 nG2.PaginationGotoPage( option );
1788 break;
1789 case 'paginationCountPages':
1790 nG2.PaginationCountPages();
1791 break;
1792
1793 }
1794 return $(this);
1795
1796 }
1797 };
1798
1799
1800 // ###############################
1801 // ##### nanogallery2 script #####
1802 // ###############################
1803
1804 /** @function nanoGALLERY2 */
1805 function nanoGALLERY2() {
1806 "use strict";
1807
1808 /**
1809 * Force reload the current album, if provided by Json
1810 */
1811 this.LightboxReOpen = function(){
1812 LightboxStandaloneDisplay();
1813 }
1814
1815 /**
1816 * Force reload the current album, if provided by Json
1817 */
1818 this.ReloadAlbum = function(){
1819 if( G.O.kind === '' ) {
1820 throw 'Not supported for this content source:' + G.O.kind;
1821 }
1822
1823 var albumIdx = G.GOM.albumIdx;
1824 if( albumIdx == -1 ) {
1825 throw ('Current album not found.');
1826 }
1827
1828 var albumID = G.I[albumIdx].GetID();
1829
1830 // unselect everything & remove link to album (=logical delete)
1831 var l = G.I.length;
1832 for( var i = 0; i < l ; i++ ) {
1833 var item = G.I[i];
1834 if( item.albumID == albumID ) {
1835 item.selected = false;
1836 }
1837 }
1838
1839 G.I[albumIdx].contentIsLoaded = false;
1840
1841 DisplayAlbum('-1', albumID);
1842 };
1843
1844 /**
1845 * Set one or several items selected/unselected
1846 * @param {array} items
1847 */
1848 this.ItemsSetSelectedValue = function(items, value){
1849 var l = items.length;
1850 for( var j = 0; j < l ; j++) {
1851 ThumbnailSelectionSet(items[j], value);
1852 }
1853 };
1854
1855 /**
1856 * Returns an array of selected items
1857 * @returns {Array}
1858 */
1859 this.ItemsSelectedGet = function(){
1860 var selectedItems = [];
1861 var l = G.I.length;
1862 for( var i = 0; i < l ; i++ ) {
1863 if( G.I[i].selected == true ) {
1864 selectedItems.push(G.I[i]);
1865 }
1866 }
1867 return selectedItems;
1868 };
1869
1870 /**
1871 * Returns the value of an option
1872 * @param {string} option
1873 * @returns {nanoGALLERY.G.O}
1874 */
1875 this.Get = function(option){
1876 return G.O[option];
1877 };
1878
1879 /**
1880 * Set a new value for a defined option
1881 * @param {string} option
1882 */
1883 this.Set = function(option, value){
1884 G.O[option] = value;
1885 switch( option ) {
1886 case 'thumbnailSelectable':
1887 ThumbnailSelectionClear();
1888 // refresh the displayed gallery
1889 GalleryRender( G.GOM.albumIdx );
1890 break;
1891 }
1892 };
1893
1894 /**
1895 * Refresh the current gallery
1896 */
1897 this.Refresh = function() {
1898 // Refresh the displayed gallery
1899 GalleryRender( G.GOM.albumIdx );
1900 };
1901 /**
1902 * Resize the current gallery
1903 */
1904 this.Resize = function() {
1905 // resize the displayed gallery
1906 GalleryResize();
1907 };
1908
1909 /**
1910 * display one item (image or gallery)
1911 * itemID syntax:
1912 * - albumID --> display one album
1913 * - albumID/imageID --> display one image
1914 */
1915 this.DisplayItem = function( itemID ) {
1916 var IDs=parseIDs( itemID );
1917 if( IDs.imageID != '0' ) {
1918 DisplayPhoto( IDs.imageID, IDs.albumID );
1919 }
1920 else {
1921 DisplayAlbum( '-1', IDs.albumID );
1922 }
1923 };
1924
1925 this.ThumbnailToolbarOneCartUpdate = function ( item ) {
1926 ThumbnailBuildToolbarOneCartUpdate( item );
1927 }
1928
1929
1930
1931 var CountItemsToDisplay = function( gIdx ) {
1932 if( G.I[gIdx] == undefined ) { return 0; }
1933 var albumID = G.I[gIdx].GetID();
1934 var l = G.I.length;
1935 var cnt = 0;
1936 for( var idx = 0; idx < l; idx++ ) {
1937 var item = G.I[idx];
1938 if( item.isToDisplay(albumID) ) {
1939 cnt++;
1940 }
1941 }
1942 return cnt;
1943 }
1944 /**
1945 * Search in the displayed gallery (in thumbnails title)
1946 */
1947 this.Search = function( search ) {
1948 G.GOM.albumSearch = search.toUpperCase();
1949 var gIdx = G.GOM.albumIdx;
1950 GalleryRender( G.GOM.albumIdx );
1951 return CountItemsToDisplay( gIdx );
1952 };
1953
1954 /**
1955 * Search2 in title and tags - set search values
1956 */
1957 this.Search2 = function( searchTitle, searchTags ) {
1958 if( searchTitle != undefined && searchTitle != null ) {
1959 G.GOM.albumSearch = searchTitle.toUpperCase().trim();
1960 }
1961 else {
1962 G.GOM.albumSearch = '';
1963 }
1964
1965 if( searchTags != null && searchTags != undefined ) {
1966 G.GOM.albumSearchTags = searchTags.toUpperCase().trim();
1967 }
1968 else {
1969 G.GOM.albumSearchTags = '';
1970 }
1971 return CountItemsToDisplay( G.GOM.albumIdx );
1972 };
1973
1974 /**
1975 * Search2 - execute the search on title and tags
1976 */
1977 this.Search2Execute = function() {
1978 var gIdx = G.GOM.albumIdx;
1979 GalleryRender( G.GOM.albumIdx );
1980 return CountItemsToDisplay( gIdx );
1981 };
1982
1983
1984 /**
1985 * Destroy the current gallery
1986 */
1987 this.Destroy = function(){
1988
1989 if( G.GOM.hammertime != null ) {
1990 G.GOM.hammertime.destroy();
1991 G.GOM.hammertime = null;
1992 }
1993
1994 if( G.VOM.hammertime != null ) {
1995 G.VOM.hammertime.destroy();
1996 G.VOM.hammertime = null;
1997 }
1998
1999 // color scheme
2000 $('#ngycs_' + G.baseEltID).remove()
2001
2002 G.GOM.items = [];
2003 NGY2Item.New( G, G.i18nTranslations.breadcrumbHome, '', '0', '-1', 'album' );
2004 G.GOM.navigationBar.$newContent = null;
2005 G.$E.base.empty();
2006 G.$E.base.removeData();
2007 if( G.O.locationHash ) {
2008 jQuery(window).off('hashchange.nanogallery2.' + G.baseEltID);
2009 }
2010
2011 jQuery(window).off('resize.nanogallery2.' + G.baseEltID);
2012 jQuery(window).off('orientationChange.nanogallery2.' + G.baseEltID);
2013 jQuery(window).off('scroll.nanogallery2.' + G.baseEltID);
2014 if( G.$E.scrollableParent !== null ) {
2015 G.$E.scrollableParent.off('scroll.nanogallery2.' + G.baseEltID);
2016 }
2017 G.GOM.firstDisplay = true;
2018 };
2019
2020 /**
2021 * CloseViewer - close the media viewer
2022 */
2023 this.CloseViewer = function() {
2024 LightboxClose(null);
2025 return false;
2026 };
2027
2028 /**
2029 * MinimizeToolbar - display the minimized lightbox main toolbar
2030 */
2031 this.MinimizeToolbar = function() {
2032 ViewerToolbarForVisibilityMin();
2033 return false;
2034 };
2035
2036 /**
2037 * MaximizeToolbar - display the maximized/standard lightbox main toolbar
2038 */
2039 this.MaximizeToolbar = function() {
2040 ViewerToolbarForVisibilityStd();
2041 return false;
2042 };
2043
2044 /**
2045 * PaginationPreviousPage - gallery paginate to previous page
2046 */
2047 this.PaginationPreviousPage = function() {
2048 paginationPreviousPage();
2049 return false;
2050 };
2051
2052
2053 /**
2054 * PaginationNextPage - gallery paginate to next page
2055 */
2056 this.PaginationNextPage = function() {
2057 paginationNextPage();
2058 return false;
2059 };
2060
2061
2062 /**
2063 * PaginationGotoPage - gallery paginate to specific page
2064 */
2065 this.PaginationGotoPage = function( page ) {
2066 // var aIdx = G.$E.conPagin.data('galleryIdx');
2067 if( page > 1 ) { page--; }
2068 G.GOM.pagination.currentPage = page;
2069
2070 // scroll to top of gallery if not displayed
2071 G.GOM.ScrollToTop();
2072
2073 GalleryDisplayPart1();
2074 GalleryDisplayPart2( true );
2075 return false;
2076 };
2077
2078 /**
2079 * PaginationCountPages - gallery pagination - returns the number of pages
2080 */
2081 this.PaginationCountPages = function() {
2082 if( G.GOM.items.length == 0 ) { return 0; } // no thumbnail to display
2083
2084 var nbPages = Math.ceil((G.GOM.items[G.GOM.items.length - 1].row + 1) / G.galleryMaxRows.Get());
2085 return nbPages;
2086 };
2087
2088 /**
2089 * PaginationCountPages - gallery pagination - returns the number of pages
2090 */
2091
2092
2093
2094 // throttle()
2095 // author: underscore.js - http://underscorejs.org/docs/underscore.html
2096 // Returns a function, that, when invoked, will only be triggered at most once during a given window of time.
2097 // Normally, the throttled function will run as much as it can, without ever going more than once per wait duration;
2098 // but if you�d like to disable the execution on the leading edge, pass {leading: false}.
2099 // To disable execution on the trailing edge, ditto.
2100 var throttle = function(func, wait, options) {
2101 var context, args, result;
2102 var timeout = null;
2103 var previous = 0;
2104 if (!options) options = {};
2105 var later = function() {
2106 previous = options.leading === false ? 0 : new Date().getTime();
2107 timeout = null;
2108 result = func.apply(context, args);
2109 if (!timeout) context = args = null;
2110 };
2111 return function() {
2112 var now = new Date().getTime();
2113 if (!previous && options.leading === false) previous = now;
2114 var remaining = wait - (now - previous);
2115 context = this;
2116 args = arguments;
2117 if (remaining <= 0 || remaining > wait) {
2118 if (timeout) {
2119 clearTimeout(timeout);
2120 timeout = null;
2121 }
2122 previous = now;
2123 result = func.apply(context, args);
2124 if (!timeout) context = args = null;
2125 } else if (!timeout && options.trailing !== false) {
2126 timeout = setTimeout(later, remaining);
2127 }
2128 return result;
2129 };
2130 };
2131
2132
2133 // DEBOUNCE
2134 // author: John Hann - http://unscriptable.com/2009/03/20/debouncing-javascript-methods/
2135 // execAsap - false means executing at the end of the detection period
2136 var debounce = function (func, threshold, execAsap) {
2137 var timeout;
2138 return function debounced () {
2139 var obj = this, args = arguments;
2140 function delayed () {
2141 if (!execAsap)
2142 func.apply(obj, args);
2143 timeout = null;
2144 };
2145
2146 if (timeout)
2147 clearTimeout(timeout);
2148 // clearRequestTimeout(timeout);
2149 else if (execAsap)
2150 func.apply(obj, args);
2151 timeout = setTimeout(delayed, threshold || 100);
2152 // timeout = requestTimeout(delayed, threshold || 100);
2153 };
2154 }
2155
2156
2157 // Double requestAnimationFrame
2158 window.ng_draf = function (cb) {
2159 return requestAnimationFrame(function() {
2160 window.requestAnimationFrame(cb)
2161 })
2162 }
2163
2164 // REQUESTTIMEOUT - replace SETTIMEOUT - https://gist.github.com/joelambert/1002116
2165 /**
2166 * Behaves the same as setTimeout except uses requestAnimationFrame() where possible for better performance
2167 * @param {function} fn The callback function
2168 * @param {int} delay The delay in milliseconds
2169 */
2170
2171 window.requestTimeout = function(fn, delay) {
2172 if( !window.requestAnimationFrame &&
2173 !window.webkitRequestAnimationFrame &&
2174 !(window.mozRequestAnimationFrame && window.mozCancelRequestAnimationFrame) && // Firefox 5 ships without cancel support
2175 !window.oRequestAnimationFrame &&
2176 !window.msRequestAnimationFrame)
2177 return window.setTimeout(fn, delay);
2178
2179 var start = new Date().getTime(),
2180 handle = new Object();
2181
2182 function loop(){
2183 var current = new Date().getTime(),
2184 delta = current - start;
2185 // delta = delay;
2186
2187 delta >= delay ? fn.call() : handle.value = requestAnimFrame(loop);
2188 };
2189
2190 handle.value = requestAnimFrame(loop);
2191 return handle;
2192 };
2193
2194
2195 // requestAnimationFrame() shim by Paul Irish
2196 // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
2197 window.requestAnimFrame = (function() {
2198 return window.requestAnimationFrame ||
2199 window.webkitRequestAnimationFrame ||
2200 window.mozRequestAnimationFrame ||
2201 window.oRequestAnimationFrame ||
2202 window.msRequestAnimationFrame ||
2203 function(/* function */ callback, /* DOMElement */ element){
2204 window.setTimeout(callback, 1000 / 60);
2205 };
2206 })();
2207
2208
2209 // CLEARREQUESTTIMEOUT - to replace CLEARTIMEOUT - https://gist.github.com/joelambert/1002116
2210 /**
2211 * Behaves the same as clearTimeout except uses cancelRequestAnimationFrame() where possible for better performance
2212 * @param {int|object} fn The callback function
2213 */
2214 window.clearRequestTimeout = function(handle) {
2215 window.cancelAnimationFrame ? window.cancelAnimationFrame(handle.value) :
2216 window.webkitCancelAnimationFrame ? window.webkitCancelAnimationFrame(handle.value) :
2217 window.webkitCancelRequestAnimationFrame ? window.webkitCancelRequestAnimationFrame(handle.value) : /* Support for legacy API */
2218 window.mozCancelRequestAnimationFrame ? window.mozCancelRequestAnimationFrame(handle.value) :
2219 window.oCancelRequestAnimationFrame ? window.oCancelRequestAnimationFrame(handle.value) :
2220 window.msCancelRequestAnimationFrame ? window.msCancelRequestAnimationFrame(handle.value) :
2221 clearTimeout(handle);
2222 };
2223
2224
2225
2226 /*
2227 ** Global data for this nanogallery2 instance
2228 **/
2229 var G=this;
2230 G.I = []; // gallery items
2231 G.Id = []; // gallery items
2232 G.O = null; // user options
2233 G.baseEltID = null; // ID of the base element
2234 G.$E = {
2235 base: null, // base element
2236 conTnParent: null, // $g_containerThumbnailsParent
2237 conLoadingB: null, // loading bar - nanoGalleryLBarOff
2238 conConsole: null, // console for error messages
2239 conNavigationBar: null, // gallery navigation bar
2240 conTnBottom: null, // container on the bottom of the gallery
2241 scrollableParent: null // first scrollable parent container
2242 };
2243 G.shoppingCart = [];
2244 G.layout = { // Layout informations
2245 internal : true,
2246 engine : '',
2247 support : { rows: false },
2248 prerequisite : { imageSize: false },
2249 SetEngine: function() {
2250
2251 if( G.layout.internal ) {
2252 if( G.tn.settings.width[G.GOM.curNavLevel][G.GOM.curWidth] == 'auto' || G.tn.settings.width[G.GOM.curNavLevel][G.GOM.curWidth] == '' ) {
2253 // do not use getH() / getW() here!
2254 G.layout.engine = 'JUSTIFIED';
2255 G.layout.support.rows = true;
2256 G.layout.prerequisite.imageSize = true;
2257 return;
2258 }
2259 if( G.tn.settings.height[G.GOM.curNavLevel][G.GOM.curWidth] == 'auto' || G.tn.settings.height[G.GOM.curNavLevel][G.GOM.curWidth] == '' ) {
2260 // do not use getH() / getW() here!
2261 G.layout.engine = 'CASCADING';
2262 G.layout.support.rows = false;
2263 G.layout.prerequisite.imageSize = true;
2264 return;
2265 }
2266
2267 if( G.tn.settings.getMosaic() != null ) {
2268 G.layout.engine = 'MOSAIC';
2269 G.layout.support.rows = true;
2270 G.layout.prerequisite.imageSize = false;
2271 return;
2272 }
2273
2274 G.layout.engine = 'GRID';
2275 G.layout.support.rows=true;
2276 // if( G.tn.opt.Get('crop') === true ) {
2277 // G.layout.prerequisite.imageSize = true;
2278 // }
2279 // else {
2280 G.layout.prerequisite.imageSize = false;
2281 // }
2282 }
2283 }
2284 };
2285 G.galleryResizeEventEnabled = false;
2286 G.galleryMaxRows = { l1: 0, lN: 0,
2287 Get: function() {
2288 return G.galleryMaxRows[G.GOM.curNavLevel];
2289 }
2290 };
2291 G.galleryMaxItems = { l1: 0, lN: 0,
2292 Get: function() {
2293 return G.galleryMaxItems[G.GOM.curNavLevel];
2294 }
2295 };
2296 G.galleryFilterTags = { l1: 0, lN: 0,
2297 Get: function() {
2298 return G.galleryFilterTags[G.GOM.curNavLevel];
2299 }
2300 };
2301 G.galleryFilterTagsMode = { l1: 0, lN: 0,
2302 Get: function() {
2303 return G.galleryFilterTagsMode[G.GOM.curNavLevel];
2304 }
2305 };
2306 G.galleryDisplayMode = { l1: 'FULLCONTENT', lN: 'FULLCONTENT',
2307 Get: function() {
2308 return G.galleryDisplayMode[G.GOM.curNavLevel];
2309 }
2310 };
2311 G.galleryLastRowFull = { l1: false, lN: false,
2312 Get: function() {
2313 return G.galleryLastRowFull[G.GOM.curNavLevel];
2314 }
2315 };
2316 G.gallerySorting = { l1: '', lN: '',
2317 Get: function() {
2318 return G.gallerySorting[G.GOM.curNavLevel];
2319 }
2320 };
2321 G.galleryDisplayTransition = { l1: 'none', lN: 'none',
2322 Get: function() {
2323 return G.galleryDisplayTransition[G.GOM.curNavLevel];
2324 }
2325 };
2326 G.galleryDisplayTransitionDuration = { l1: 500, lN: 500,
2327 Get: function() {
2328 return G.galleryDisplayTransitionDuration[G.GOM.curNavLevel];
2329 }
2330 };
2331 G.$currentTouchedThumbnail = null;
2332
2333
2334
2335
2336 // ##### GENERAL THUMBNAILS PROPERTIES -->
2337 G.tn = {
2338 // levell specific options
2339 opt: {
2340 l1: { crop: true, stacks: 0, stacksTranslateX: 0, stacksTranslateY: 0, stacksTranslateZ: 0, stacksRotateX: 0, stacksRotateY: 0, stacksRotateZ: 0, stacksScale: 0, borderHorizontal: 0, borderVertical: 0, baseGridHeight: 0, displayTransition: 'FADEIN', displayTransitionStartVal: 0, displayTransitionEasing: 'easeOutQuart', displayTransitionDuration: 240, displayInterval: 15 },
2341 lN: { crop: true, stacks: 0, stacksTranslateX: 0, stacksTranslateY: 0, stacksTranslateZ: 0, stacksRotateX: 0, stacksRotateY: 0, stacksRotateZ: 0, stacksScale: 0, borderHorizontal: 0, borderVertical: 0, baseGridHeight: 0, displayTransition: 'FADEIN', displayTransitionStartVal: 0, displayTransitionEasing: 'easeOutQuart', displayTransitionDuration: 240, displayInterval: 15 },
2342 Get: function(opt) {
2343 return G.tn.opt[G.GOM.curNavLevel][opt];
2344 }
2345 },
2346 scale: 1, // image scale depending of the hover effect
2347 labelHeight: { // in case label on bottom, otherwise always=0
2348 l1: 0, lN: 0,
2349 get: function() {
2350 return G.tn.labelHeight[G.GOM.curNavLevel];
2351 }
2352 },
2353 defaultSize: { // default thumbnail size
2354 // label height is not included
2355 width: { l1 : { xs:0, sm:0, me:0, la:0, xl:0 }, lN : { xs:0, sm:0, me:0, la:0, xl:0 } },
2356 height: { l1 : { xs:0, sm:0, me:0, la:0, xl:0 }, lN : { xs:0, sm:0, me:0, la:0, xl:0 } },
2357 getWidth: function() {
2358 return G.tn.defaultSize.width[G.GOM.curNavLevel][G.GOM.curWidth];
2359 },
2360 getOuterWidth: function() { // width including border
2361 G.tn.borderWidth = G.tn.opt.Get('borderHorizontal');
2362 G.tn.borderHeight = G.tn.opt.Get('borderVertical');
2363 var w = G.tn.defaultSize.width[G.GOM.curNavLevel][G.GOM.curWidth] + G.tn.opt.Get('borderHorizontal') * 2;
2364 if( G.O.thumbnailLabel.get('position') == 'right' || G.O.thumbnailLabel.get('position') == 'left' ) {
2365 w += G.tn.defaultSize.width[G.GOM.curNavLevel][G.GOM.curWidth];
2366 }
2367 return w;
2368 },
2369 getHeight: function() {
2370 return G.tn.defaultSize.height[G.GOM.curNavLevel][G.GOM.curWidth];
2371 },
2372 getOuterHeight: function() { // height, border included
2373 return G.tn.defaultSize.height[G.GOM.curNavLevel][G.GOM.curWidth]+G.tn.opt.Get('borderVertical')*2;
2374 }
2375 },
2376 settings: { // user defined width/height of the image to display depending on the screen size
2377 width: { l1 : { xs:0, sm:0, me:0, la:0, xl:0, xsc:'u', smc:'u', mec:'u', lac:'u', xlc:'u' },
2378 lN : { xs:0, sm:0, me:0, la:0, xl:0, xsc:'u', smc:'u', mec:'u', lac:'u', xlc:'u' } },
2379 height: { l1 : { xs:0, sm:0, me:0, la:0, xl:0, xsc:'u', smc:'u', mec:'u', lac:'u', xlc:'u' },
2380 lN : { xs:0, sm:0, me:0, la:0, xl:0, xsc:'u', smc:'u', mec:'u', lac:'u', xlc:'u' } },
2381 getH: function(l, w) {
2382 var cl = (l == undefined ? G.GOM.curNavLevel : l);
2383 var cw = (w == undefined ? G.GOM.curWidth : w);
2384 if( G.layout.engine == 'MOSAIC' ) {
2385 return this.height[cl][cw] * this.mosaic[cl+'Factor']['h'][cw];
2386 }
2387 else {
2388 return this.height[cl][cw];
2389 }
2390 },
2391 getW: function(l, w) {
2392 var cl = (l == undefined ? G.GOM.curNavLevel : l);
2393 var cw = (w == undefined ? G.GOM.curWidth : w);
2394 if( G.layout.engine == 'MOSAIC' ) {
2395 return this.width[cl][cw] * this.mosaic[cl+'Factor']['w'][cw];
2396 }
2397 else {
2398 return this.width[cl][cw];
2399 // return G.tn.settings.width[G.GOM.curNavLevel][G.GOM.curWidth];
2400 }
2401 },
2402 mosaic: { l1 : { xs: null, sm: null, me: null, la: null, xl: null },
2403 lN : { xs: null, sm: null, me: null, la: null, xl: null },
2404 l1Factor : { h :{ xs: 1, sm: 1, me: 1, la: 1, xl: 1 }, w :{ xs: 1, sm: 1, me: 1, la: 1, xl: 1 }},
2405 lNFactor : { h :{ xs: 1, sm: 1, me: 1, la: 1, xl: 1 }, w :{ xs: 1, sm: 1, me: 1, la: 1, xl: 1 }}
2406 },
2407 getMosaic: function() {
2408 return this.mosaic[G.GOM.curNavLevel][G.GOM.curWidth];
2409 },
2410 mosaicCalcFactor: function(l, w) {
2411 // retrieve max size multiplicator
2412 var maxW = 1;
2413 var maxH = 1;
2414 for( var n = 0; n < G.tn.settings.mosaic[l][w].length; n++ ) {
2415 maxW = Math.max(maxW, this.mosaic[l][w][n]['w']);
2416 maxH = Math.max(maxH, this.mosaic[l][w][n]['h']);
2417 }
2418 this.mosaic[l + 'Factor']['h'][w] = maxH;
2419 this.mosaic[l + 'Factor']['w'][w] = maxW;
2420 },
2421 gutterHeight: { l1 : { xs:0, sm:0, me:0, la:0, xl:0 }, lN : { xs:0, sm:0, me:0, la:0, xl:0 } },
2422 gutterWidth: { l1 : { xs:0, sm:0, me:0, la:0, xl:0 }, lN : { xs:0, sm:0, me:0, la:0, xl:0 } },
2423 GetResponsive: function( setting ) {
2424 return this[setting][G.GOM.curNavLevel][G.GOM.curWidth];
2425 }
2426 },
2427 // thumbnail hover effects
2428 hoverEffects : {
2429 std : [],
2430 level1: [],
2431 get: function() {
2432 if( G.GOM.curNavLevel == 'l1' && this.level1.length !== 0 ) {
2433 return this.level1;
2434 }
2435 else {
2436 return this.std;
2437 }
2438 }
2439 },
2440 // thumbnail init
2441 buildInit : {
2442 std : [],
2443 level1: [],
2444 get: function() {
2445 if( G.GOM.curNavLevel == 'l1' && this.level1.length !== 0 ) {
2446 return this.level1;
2447 }
2448 else {
2449 return this.std;
2450 }
2451 }
2452 },
2453 // thumbnail toolbars
2454 toolbar: {
2455 album : { topLeft : '', topRight: '', bottomLeft: '', bottomRight: '' },
2456 image : { topLeft : '', topRight: '', bottomLeft: '', bottomRight: '' },
2457 albumUp : { topLeft : '', topRight: '', bottomLeft: '', bottomRight: '' },
2458 get: function( item ) {
2459 return this[item.kind];
2460 },
2461 },
2462 style: {
2463 // inline CSS
2464 l1 : { label: '', title: '', desc: '' },
2465 lN : { label: '', title: '', desc: '' },
2466 getTitle : function() {
2467 return ('style="' + this[G.GOM.curNavLevel].title + '"');
2468 },
2469 getDesc : function() {
2470 return ('style="' + this[G.GOM.curNavLevel].desc + '"');
2471 },
2472 getLabel: function() {
2473 var s='style="'+ this[G.GOM.curNavLevel].label;
2474 s+= (G.O.RTL ? '"direction:RTL;"' :'');
2475 s+='"';
2476 return s;
2477 }
2478 }
2479 };
2480 G.scrollTimeOut = 0;
2481 G.i18nTranslations = {'paginationPrevious':'Previous', 'paginationNext':'Next', 'breadcrumbHome':'List of Albums', 'thumbnailImageTitle':'', 'thumbnailAlbumTitle':'', 'thumbnailImageDescription':'', 'thumbnailAlbumDescription':'' };
2482 G.emptyGif = 'data:image/gif;base64,R0lGODlhEAAQAIAAAP///////yH5BAEKAAEALAAAAAAQABAAAAIOjI+py+0Po5y02ouzPgUAOw==';
2483 G.CSStransformName = FirstSupportedPropertyName(["transform", "msTransform", "MozTransform", "WebkitTransform", "OTransform"]);
2484 // G.CSSfilterName = FirstSupportedPropertyName(["filter", "WebkitFilter"]);
2485 G.CSStransformStyle = FirstSupportedPropertyName(["transformStyle", "msTransformStyle", "MozTransformStyle", "WebkitTransformStyle", "OTransformStyle"]);
2486 G.CSSperspective = FirstSupportedPropertyName(["perspective", "msPerspective", "MozPerspective", "WebkitPerspective", "OPerspective"]);
2487 G.CSSbackfaceVisibilityName = FirstSupportedPropertyName(["backfaceVisibility", "msBackfaceVisibility", "MozBackfaceVisibility", "WebkitBackfaceVisibility", "OBackfaceVisibility"]);
2488 G.CSStransitionName = FirstSupportedPropertyName(["transition", "msTransition", "MozTransition", "WebkitTransition", "OTransition"]);
2489 G.CSSanimationName = FirstSupportedPropertyName(["animation", "msAnimation", "MozAnimation", "WebkitAnimation", "OAnimation"]);
2490 G.GalleryResizeThrottled = throttle(GalleryResize, 15, {leading: false});
2491
2492 G.blockList = null; // album names - block list
2493 G.allowList = null; // album names - allow list
2494 G.albumList = []; // album list
2495 G.locationHashLastUsed = '';
2496 G.custGlobals = {};
2497 G.touchAutoOpenDelayTimerID = 0;
2498 G.i18nLang = '';
2499 G.timeLastTouchStart = 0;
2500 G.custGlobals = {};
2501 G.markupOrApiProcessed = false;
2502
2503 //------------------------
2504 //--- Gallery Object Model
2505 G.GOM = {
2506 albumIdx : -1, // index (in G.I) of the currently displayed album
2507 clipArea : { top: 0, height: 0 }, // area of the GOM to display on screen
2508 displayArea : { width: 0 , height: 0 }, // size of the GOM area (=used area, not available area)
2509 displayAreaLast : { width: 0 , height: 0 }, // previous size of the GOM area
2510 displayedMoreSteps : 0, // current number of displayed steps (moreButton mode)
2511 items: [], // current items of the GOMS
2512 $imgPreloader: [],
2513 thumbnails2Display: [],
2514 itemsDisplayed : 0, // number of currently displayed thumbnails
2515 firstDisplay : true,
2516 firstDisplayTime : 0, // in conjunction with galleryRenderDelay
2517 navigationBar : { // content of the navigation bar (for breadcrumb, filter tags and next/previous pagination)
2518 displayed: false,
2519 $newContent: ''
2520 },
2521 cache : { // cached data
2522 viewport: null,
2523 containerOffset: null,
2524 areaWidth: 100 // available area width
2525 },
2526 nbSelected : 0, // number of selected items
2527 pagination : { currentPage: 0 }, // pagination data
2528 panThreshold: 60, // threshold value (in pixels) to block horizontal pan/swipe
2529 panYOnly: false, // threshold value reach -> definitively block horizontal pan until end of pan
2530 lastFullRow : -1, // number of the last row without holes
2531 lastDisplayedIdx: -1, // used to display the counter of not displayed items
2532 displayInterval : { from: 0, len: 0 },
2533 hammertime: null,
2534 curNavLevel: 'l1', // current navigation level (l1 or LN)
2535 curWidth: 'me',
2536 albumSearch: '', // current search string -> title (used to filter the thumbnails on screen)
2537 albumSearchTags: '', // current search string -> tags
2538 lastZIndex: 0, // used to put a thumbnail on top of all others (for exemple for scale hover effect)
2539 lastRandomValue: 0,
2540 slider : { // slider on last thumbnail
2541 hostIdx: -1, // idx of the thumbnail hosting the slider
2542 hostItem: null, // item hosting the slider
2543 currentIdx: 0, // idx of the current displayed item
2544 nextIdx: 0, // idx of the next item to display in the slider
2545 timerID: 0,
2546 tween: null // tranistion tween instance
2547 },
2548 NGY2Item: function( idx ) { // returns a NGY2Item or null if it does not exist
2549 if( G.GOM.items[idx] == undefined || G.GOM.items[idx] == null ) { return null; }
2550 var i = G.GOM.items[idx].thumbnailIdx;
2551 return G.I[i];
2552 },
2553 // One GOM item (thumbnail)
2554 // function GTn(index, width, height) {
2555 GTn: function(index, width, height) {
2556 this.thumbnailIdx = index;
2557 this.width = 0; // thumbnail width
2558 this.height = 0; // thumbnail height
2559 this.top = 0; // position: top
2560 this.left = 0; // position: left
2561 this.row = 0; // position: row number
2562 this.imageWidth = width; // image width
2563 this.imageHeight = height; // image height
2564 this.resizedContentWidth = 0;
2565 this.resizedContentHeight = 0;
2566 this.displayed = false;
2567 this.neverDisplayed = true;
2568 this.inDisplayArea = false;
2569 },
2570 // Position the top of the gallery to make it visible, if not displayed
2571 ScrollToTop: function() {
2572
2573 if( G.GOM.firstDisplay ) {
2574 // do no scroll to top on first display
2575 return;
2576 }
2577
2578 if( G.$E.scrollableParent === null && !topInViewportVert(G.$E.base, 20) ) {
2579 // $('html, body').animate({scrollTop: G.$E.base.offset().top}, 200);
2580 G.$E.base.get(0).scrollIntoView();
2581 }
2582
2583 if( G.$E.scrollableParent !== null ) {
2584 // gallery in a scrollable container: check if we have to scroll up so that the top of the gallery is visible
2585 // vertical position of the scrollbar
2586 var scrollTop = G.$E.scrollableParent.scrollTop();
2587 // top of the gallery relative to the top of the scrollable container
2588 var dist = Math.abs(G.$E.scrollableParent.offset().top - G.$E.base.offset().top - scrollTop);
2589 if( scrollTop > dist ) {
2590 window.ng_draf( function() {
2591 // we need a little delay before setting the new scrollbar (but why?....)
2592 G.$E.scrollableParent.scrollTop(dist);
2593 });
2594 }
2595 }
2596 }
2597 };
2598
2599
2600 //------------------------
2601 //--- Viewer Object Model
2602
2603 G.VOM = {
2604 viewerDisplayed: false, // is the viewer currently displayed
2605 viewerIsFullscreen: false, // viewer in fullscreen mode
2606 infoDisplayed: false, // is the info box displayed
2607 toolbarsDisplayed: true, // the toolbars are displayed
2608 toolsHide: null,
2609 zoom : {
2610 posX: 0, // position to center zoom in/out
2611 posY: 0,
2612 userFactor: 1, // user zoom factor (applied to the baseZoom factor)
2613 isZooming: false
2614 },
2615 padding: { H: 0, V: 0 }, // padding for the image
2616 window: { lastWidth: 0, lastHeight: 0 },
2617 $viewer: null,
2618 $toolbar: null, // viewerToolbar
2619 $toolbarTL: null, // viewer toolbar on top left
2620 $toolbarTR: null, // viewer toolbar on top right
2621
2622 toolbarMode: 'std', // current toolbar mode (standard, minimized)
2623 playSlideshow : false, // slide show mode status
2624 playSlideshowTimerID: 0, // slideshow mode time
2625 slideshowDelay: 3000, // slideshow mode - delay before next image
2626 albumID: -1,
2627 viewerMediaIsChanged: false, // media display is currently modified
2628 items: [], // current list of images to be managed by the viewer
2629
2630 panMode: 'off', // if panning, which element -> media, gallery, or zoom - if not -> off
2631
2632 $baseCont: null, // lightbox container
2633 $content: null, // pointer to the 3 media in the viewer
2634 content: {
2635 previous : {
2636 vIdx: -1,
2637 $media: null,
2638 NGY2Item: function() {
2639 return G.I[ G.VOM.items[G.VOM.content.previous.vIdx].ngy2ItemIdx ];
2640 }
2641 },
2642 current : {
2643 vIdx: -1,
2644 $media: null,
2645 NGY2Item: function() {
2646 return G.I[ G.VOM.items[G.VOM.content.current.vIdx].ngy2ItemIdx ];
2647 }
2648 },
2649 next : {
2650 vIdx: -1,
2651 $media: null,
2652 NGY2Item: function() {
2653 return G.I[ G.VOM.items[G.VOM.content.next.vIdx].ngy2ItemIdx ];
2654 }
2655 }
2656 },
2657 IdxNext: function() {
2658 var n = 0;
2659 // if( G.VOM.currItemIdx <= (G.VOM.items.length-1) ) {
2660 if( G.VOM.content.current.vIdx < (G.VOM.items.length-1) ) {
2661 n = G.VOM.content.current.vIdx + 1;
2662 }
2663 return n;
2664 },
2665 IdxPrevious: function() {
2666 var n = G.VOM.content.current.vIdx - 1;
2667 if( G.VOM.content.current.vIdx == 0 ) {
2668 n = G.VOM.items.length - 1;
2669 }
2670 return n;
2671 },
2672
2673 gallery: {
2674 $elt: null, // Base container
2675 $tmbCont: null, // Thumbnail container
2676 gwidth: 0, // thumbnail container width (all thumbnails)
2677 vwidth: 0, // visible width of the gallery (just for the visible thumbnails)
2678 oneTmbWidth: 0,
2679 firstDisplay: true,
2680 posX: 0,
2681 SetThumbnailActive() {
2682 if( G.O.viewerGallery == 'none' ) { return; }
2683 this.$tmbCont.children().removeClass('activeVThumbnail');
2684 this.$tmbCont.children().eq( G.VOM.content.current.vIdx ).addClass('activeVThumbnail');
2685 this.firstDisplay = false;
2686 },
2687 Resize: function() {
2688 if( G.O.viewerGallery == 'none' ) { return; }
2689
2690 if( !this.firstDisplay ) {
2691 var viewerW = G.VOM.$viewer.width();
2692
2693 // Center base element
2694 var maxTmb = Math.trunc(viewerW / this.oneTmbWidth); // max thumbnail that can be displayed
2695 this.vwidth = maxTmb * this.oneTmbWidth;
2696 this.$elt.css({ width: this.vwidth, left: (viewerW - this.vwidth)/2 });
2697
2698 // Set the position the thumbnails container (if there's no enough space for all thumbnails)
2699 if( G.VOM.items.length >= maxTmb ) {
2700 var tmbPos = this.oneTmbWidth * G.VOM.content.current.vIdx; // left position of the selected thumbnail
2701
2702 if( (tmbPos + this.posX) < this.vwidth ) {
2703 if( tmbPos + this.posX < 0 ) {
2704 this.posX = -tmbPos;
2705 }
2706 }
2707 else {
2708 if( tmbPos + this.posX >= this.vwidth ) {
2709 this.posX = this.vwidth - (tmbPos + this.oneTmbWidth)
2710 }
2711 }
2712 }
2713
2714 this.PanGallery(0);
2715 }
2716 else {
2717 // first display of the gallery -> opacity transition
2718 new NGTweenable().tween({
2719 from: { opacity: 0 },
2720 to: { opacity: 1 },
2721 easing: 'easeInOutSine',
2722 duration: 1000,
2723 step: function (state) {
2724 // G.VOM.gallery.$elt.css( state );
2725 },
2726 finish: function (state) {
2727 // G.VOM.gallery.$elt.css({ opacity: 1});
2728 }
2729 });
2730
2731 }
2732 },
2733 PanGallery: function( panX ){
2734
2735 // all thumbnails are visible -> center the base element
2736 if( this.gwidth < G.VOM.$viewer.width() ) { // this.oneTmbWidth
2737 this.posX = (G.VOM.$viewer.width() - this.gwidth) / 2;
2738 panX = 0; // block pan
2739 }
2740
2741 // if( this.posX > (this.vwidth - this.oneTmbWidth) ) {
2742 if( this.posX > (this.vwidth - this.oneTmbWidth) ) {
2743 // gallery is outside of the screen, right side
2744 this.posX = this.vwidth - this.oneTmbWidth;
2745 }
2746 if( (this.posX+this.gwidth) < this.oneTmbWidth ) {
2747 // gallery is outside of the screen, left side
2748 this.posX = -this.gwidth + this.oneTmbWidth;
2749 }
2750
2751 this.$tmbCont.css( G.CSStransformName , 'translateX(' + (this.posX + panX) + 'px)');
2752 },
2753 PanGalleryEnd: function( velocity ) { // velocity = pixels/millisecond
2754
2755 var d = velocity * 100; // distance
2756 new NGTweenable().tween({
2757 from: { pan: G.VOM.gallery.posX },
2758 to: { pan: G.VOM.gallery.posX + d },
2759 easing: 'easeOutQuad',
2760 duration: 500,
2761 step: function (state) {
2762 G.VOM.gallery.posX = state.pan;
2763 G.VOM.gallery.PanGallery( 0 );
2764 }
2765 });
2766 }
2767
2768 },
2769 hammertime: null, // hammer.js manager
2770 swipePosX: 0, // current horizontal swip position
2771 panPosX: 0, // position for manual pan
2772 panPosY: 0,
2773 panThreshold: 60, // threshold value (in pixels) to block vertical pan
2774 panXOnly: false, // threshold value reach -> definitively block vertical pan until end of pan
2775 singletapTime: 0,
2776 viewerTheme: '',
2777 timeImgChanged: 0,
2778 ImageLoader: {
2779 // fires a callback when image size is know (during download)
2780 // inspired by ROB - http://stackoverflow.com/users/226507/rob
2781 maxChecks: 1000,
2782 list: [],
2783 intervalHandle : null,
2784
2785 loadImage : function (callback, ngitem) {
2786 if( ngitem.mediaKind != 'img' ) { return; } // ignore - only for images
2787 var img = new Image ();
2788 img.src = ngitem.responsiveURL();
2789 if (img.width && img.height) {
2790 callback (img.width, img.height, ngitem, 0);
2791 }
2792 else {
2793 var obj = {image: img, url: ngitem.responsiveURL(), ngitem: ngitem, callback: callback, checks: 1};
2794 var i;
2795 for (i=0; i < this.list.length; i++) {
2796 if (this.list[i] == null)
2797 break;
2798 }
2799 this.list[i] = obj;
2800 if (!this.intervalHandle)
2801 this.intervalHandle = setInterval(this.interval, 50);
2802 }
2803 },
2804
2805 // called by setInterval
2806 interval : function () {
2807 var count = 0;
2808 var list = G.VOM.ImageLoader.list, item;
2809 for (var i=0; i<list.length; i++) {
2810 item = list[i];
2811 if (item != null) {
2812 if (item.image.width && item.image.height) {
2813 G.VOM.ImageLoader.list[i] = null;
2814 item.callback (item.image.width, item.image.height, item.ngitem, item.checks);
2815 }
2816 else if (item.checks > G.VOM.ImageLoader.maxChecks) {
2817 G.VOM.ImageLoader.list[i] = null;
2818 item.callback (0, 0, item.ngitem, item.checks);
2819 }
2820 else {
2821 count++;
2822 item.checks++;
2823 }
2824 }
2825 }
2826 if (count == 0) {
2827 G.VOM.ImageLoader.list = [];
2828 clearInterval (G.VOM.ImageLoader.intervalHandle);
2829 delete G.VOM.ImageLoader.intervalHandle;
2830 }
2831 }
2832 }
2833 }
2834 // One VOM item (image)
2835 function VImg( index ) {
2836 this.$e = null;
2837 this.ngy2ItemIdx = index;
2838 this.mediaNumber = G.VOM.items.length + 1;
2839 this.posX = 0; // to center the element
2840 this.posY = 0;
2841 }
2842
2843
2844 //------------------------
2845 //--- popup
2846 G.popup = {
2847 isDisplayed: false,
2848 $elt: null,
2849 close: function() {
2850 if( this.$elt != null ) {
2851 var tweenable = new NGTweenable();
2852 tweenable.tween({
2853 from: { opacity:1 },
2854 to: { opacity:0 },
2855 attachment: { t: this },
2856 easing: 'easeInOutSine',
2857 duration: 100,
2858 step: function (state, att) {
2859 if( att.t.$elt != null ) {
2860 att.t.$elt.css('opacity',state.opacity);
2861 }
2862 },
2863 finish: function (state, att) {
2864 if( att.t.$elt != null ) {
2865 att.t.$elt.remove();
2866 att.t.$elt=null;
2867 }
2868 att.t.isDisplayed=false;
2869 }
2870 });
2871 }
2872 }
2873 }
2874
2875
2876 // Color schemes - Gallery
2877 // Gradient generator: https://www.grabient.com/
2878 G.galleryTheme_dark = {
2879 navigationBar : { background: 'none', borderTop: '', borderBottom: '', borderRight: '', borderLeft: '' },
2880 navigationBreadcrumb : { background: '#111', color: '#fff', colorHover: '#ccc', borderRadius: '4px' },
2881 navigationFilter : { color: '#ddd', background: '#111', colorSelected: '#fff', backgroundSelected: '#111', borderRadius: '4px' },
2882 navigationPagination : { background: '#111', color: '#fff', colorHover: '#ccc', borderRadius: '4px' },
2883 thumbnail : { background: '#444', backgroundImage: 'linear-gradient(315deg, #111 0%, #445 90%)', borderColor: '#000', borderRadius: '0px', labelOpacity : 1, labelBackground: 'rgba(34, 34, 34, 0)', titleColor: '#fff', titleBgColor: 'transparent', titleShadow: '', descriptionColor: '#ccc', descriptionBgColor: 'transparent', descriptionShadow: '', stackBackground: '#aaa' },
2884 thumbnailIcon : { padding: '5px', color: '#fff', shadow:'' },
2885 pagination : { background: '#181818', backgroundSelected: '#666', color: '#fff', borderRadius: '2px', shapeBorder: '3px solid #666', shapeColor: '#444', shapeSelectedColor: '#aaa'}
2886 };
2887
2888 G.galleryTheme_light = {
2889 navigationBar : { background: 'none', borderTop: '', borderBottom: '', borderRight: '', borderLeft: '' },
2890 navigationBreadcrumb : { background: '#eee', color: '#000', colorHover: '#333', borderRadius: '4px' },
2891 navigationFilter : { background: '#eee', color: '#222', colorSelected: '#000', backgroundSelected: '#eee', borderRadius: '4px' },
2892 navigationPagination : { background: '#eee', color: '#000', colorHover: '#333', borderRadius: '4px' },
2893 thumbnail : { background: '#444', backgroundImage: 'linear-gradient(315deg, #111 0%, #445 90%)', borderColor: '#000', labelOpacity : 1, labelBackground: 'rgba(34, 34, 34, 0)', titleColor: '#fff', titleBgColor: 'transparent', titleShadow: '', descriptionColor: '#ccc', descriptionBgColor: 'transparent', descriptionShadow: '', stackBackground: '#888' },
2894 thumbnailIcon : { padding: '5px', color: '#fff' },
2895 pagination : { background: '#eee', backgroundSelected: '#aaa', color: '#000', borderRadius: '2px', shapeBorder: '3px solid #666', shapeColor: '#444', shapeSelectedColor: '#aaa'}
2896 };
2897
2898 // Color schemes - lightbox
2899 G.viewerTheme_dark = {
2900 background: '#000',
2901 barBackground: 'rgba(4, 4, 4, 0.2)',
2902 barBorder: '0px solid #111',
2903 barColor: '#fff',
2904 barDescriptionColor: '#ccc'
2905 };
2906 G.viewerTheme_light = {
2907 background: '#f8f8f8',
2908 barBackground: 'rgba(4, 4, 4, 0.7)',
2909 barBorder: '0px solid #111',
2910 barColor: '#fff',
2911 barDescriptionColor: '#ccc'
2912 };
2913
2914
2915
2916 // shortcut with G context to NGY2TOOLS
2917 // var NanoAlert = NGY2Tools.NanoAlert.bind(G);
2918 // var NanoConsoleLog = NGY2Tools.NanoConsoleLog.bind(G);
2919 var NanoAlert = NGY2Tools.NanoAlert;
2920 var NanoConsoleLog = NGY2Tools.NanoConsoleLog;
2921
2922
2923 /** @function initiateGallery2 */
2924 this.initiateGallery2 = function( element, params ) {
2925
2926 // GLOBAL OPTIONS
2927 G.O = params;
2928 // Base element
2929 G.$E.base = jQuery(element);
2930 G.baseEltID = G.$E.base.attr('id');
2931 if( G.baseEltID == undefined ) {
2932 // set a default ID to the root container
2933 var base_id = 'my_nanogallery';
2934 var c = '';
2935 var f = true;
2936 while( f ) {
2937 if (document.getElementById(base_id + c)) {
2938 // ID already exists
2939 if( c == '' ) {
2940 c = 1;
2941 }
2942 else {
2943 c++;
2944 }
2945 }
2946 else {
2947 f = false;
2948 G.baseEltID = 'my_nanogallery' + c;
2949 }
2950 }
2951 G.$E.base.attr('id', G.baseEltID)
2952 }
2953 G.O.$markup = [];
2954
2955 DefineVariables();
2956 SetPolyFills();
2957 BuildSkeleton();
2958 G.GOM.firstDisplayTime = Date.now();
2959
2960 SetGlobalEvents();
2961
2962 // check if only one specific album will be used
2963 if( !G.O.lightboxStandalone ) {
2964 var albumToDisplay = G.O.album;
2965 if( albumToDisplay == '' && G.O.photoset != '' ) {
2966 albumToDisplay = G.O.photoset;
2967 G.O.album = G.O.photoset;
2968 }
2969 if( albumToDisplay != '' ) {
2970 G.O.displayBreadcrumb = false; // no breadcrumb since only 1 album
2971 if( albumToDisplay.toUpperCase() != 'NONE' ) {
2972 // open a public album
2973 if( G.O.kind == "nano_photos_provider2") {
2974 if( albumToDisplay == decodeURIComponent(albumToDisplay)) {
2975 // album ID must be encoded
2976 albumToDisplay = encodeURIComponent(albumToDisplay);
2977 G.O.album = albumToDisplay;
2978 }
2979 }
2980 NGY2Item.New( G, '', '', albumToDisplay, '-1', 'album' );
2981 if( !ProcessLocationHash() ) {
2982 DisplayAlbum('-1', albumToDisplay);
2983 }
2984 return;
2985 }
2986 }
2987 }
2988
2989 // use full content
2990 // add base album
2991 NGY2Item.New( G, G.i18nTranslations.breadcrumbHome, '', '0', '-1', 'album' );
2992
2993 processStartOptions();
2994
2995 }
2996
2997
2998 /** @function processStartOptions */
2999 function processStartOptions() {
3000 // open image or album
3001 // 1. load hidden albums
3002 // 2. check if location hash set (deep linking)
3003 // 3. check openOnStart parameter
3004 // 4. open root album (ID=-1)
3005
3006 // hidden/private albums are loaded on plugin start (Picasa) --> no more available in Google Photos
3007 // if( G.albumListHidden.length > 0 ) {
3008 // jQuery.nanogallery2['data_'+G.O.kind](G, 'GetHiddenAlbums', G.albumListHidden, processStartOptionsPart2);
3009 // return;
3010 //}
3011
3012 if( !ProcessLocationHash() ) {
3013 processStartOptionsPart2();
3014 }
3015 }
3016
3017 /** @function processStartOptionsPart2 */
3018 function processStartOptionsPart2() {
3019
3020 // Check location hash + start parameters -> determine what to do on start
3021 if( G.O.lightboxStandalone ) {
3022 LightboxStandaloneFindContent();
3023 }
3024 else {
3025 // openOnStart parameter
3026 if( G.O.openOnStart != '' ) {
3027 var IDs = parseIDs(G.O.openOnStart);
3028 if( IDs.imageID != '0' ) {
3029 DisplayPhoto(IDs.imageID, IDs.albumID);
3030 }
3031 else {
3032 DisplayAlbum('-1', IDs.albumID);
3033 }
3034 }
3035 else {
3036 // open root album (ID = -1)
3037 DisplayAlbum('-1', 0);
3038 }
3039 }
3040 }
3041
3042
3043 // Lightbox standaone -> retrieve the items to display
3044 // Each item needs at least a thumbnail image and a big image
3045 // ONLY IMAGES SUPPORTED
3046 function LightboxStandaloneFindContent() {
3047
3048 G.GOM.curNavLevel = 'l1';
3049
3050 if( G.O.items == null ) {
3051 // retrieve all element having "data-nanogallery2-lightbox" and from the same group if defined
3052 var elts = jQuery('[data-nanogallery2-Lightbox');
3053 // element group
3054 var g = G.$E.base[0].dataset.nanogallery2Lgroup;
3055
3056 GetContentMarkup( elts, g );
3057 }
3058 else {
3059 // Content defined in the starting parameters
3060 GetContentApiObject();
3061 }
3062
3063 LightboxStandaloneDisplay();
3064
3065 }
3066
3067
3068 // Populate G.VOM.items + open the lightbox
3069 function LightboxStandaloneDisplay() {
3070
3071 G.VOM.items = [];
3072 G.VOM.albumID = '0';
3073 G.GOM.curNavLevel = 'l1';
3074 var vcnt = 0;
3075
3076 var srct = G.$E.base[0].src;
3077 var displayIdx = undefined;
3078 for( var idx = 0; idx < G.I.length; idx++ ) {
3079 if( G.I[idx].kind == 'image' ) {
3080 var vimg = new VImg(idx);
3081 G.VOM.items.push(vimg);
3082
3083 if( G.I[idx].thumbImg().src == srct ) {
3084 // same thumbnail URL
3085 displayIdx = vcnt;
3086 }
3087 vcnt++;
3088 }
3089
3090 }
3091 if( G.VOM.items.length > 0 ) {
3092 LightboxOpen( displayIdx );
3093 }
3094 else {
3095 NanoConsoleLog(G, 'No content for Lightbox standalone.');
3096 }
3097 }
3098
3099
3100
3101 // Parse string to extract albumID and imageID (format albumID/imageID)
3102 function parseIDs( IDs ) {
3103 var r = { albumID: '0', imageID: '0' };
3104
3105 var t = IDs.split('/');
3106 if( t.length > 0 ) {
3107 r.albumID = t[0];
3108 if( t.length > 1 ) {
3109 r.imageID = t[1];
3110 }
3111 }
3112 return r;
3113 }
3114
3115
3116 /** @function DisplayAlbum */
3117 function DisplayAlbum( imageID, albumID ) {
3118 // close viewer if already displayed
3119 if( G.VOM.viewerDisplayed ) {
3120 LightboxClose(null);
3121 }
3122
3123 // set current navigation level (l1 or lN)
3124 var albumIdx = NGY2Item.GetIdx(G, albumID);
3125 G.GOM.curNavLevel = 'lN';
3126 if( albumIdx == 0 ) {
3127 G.GOM.curNavLevel = 'l1';
3128 }
3129 G.layout.SetEngine();
3130 G.galleryResizeEventEnabled = false;
3131
3132 if( albumIdx == -1 ) {
3133 NGY2Item.New( G, '', '', albumID, '0', 'album' ); // create empty album
3134 albumIdx = G.I.length - 1;
3135 }
3136
3137 if( !G.I[albumIdx].contentIsLoaded ) {
3138 // get content of the album if not already loaded
3139 AlbumGetContent( albumID, DisplayAlbum, imageID, albumID );
3140 return;
3141 }
3142
3143 ThumbnailSelectionClear();
3144
3145 G.GOM.pagination.currentPage = 0;
3146 SetLocationHash( albumID, '' );
3147 GalleryRender( albumIdx );
3148
3149 }
3150
3151
3152 //----- manage the bottom area of the gallery -> "pagination" or "more button"
3153 function GalleryBottomManage() {
3154
3155 switch( G.galleryDisplayMode.Get() ) {
3156 case 'PAGINATION':
3157 if( G.layout.support.rows && G.galleryMaxRows.Get() > 0 ) {
3158 ManagePagination();
3159 }
3160 break;
3161 case 'MOREBUTTON':
3162 G.$E.conTnBottom.off('click');
3163 var nb = G.GOM.items.length-G.GOM.itemsDisplayed;
3164 if( nb == 0 ) {
3165 G.$E.conTnBottom.empty();
3166 }
3167 else {
3168 G.$E.conTnBottom.html('<div class="nGY2GalleryMoreButton"><div class="nGY2GalleryMoreButtonAnnotation">+'+nb+' ' + G.O.icons.galleryMoreButton +'</div></div>');
3169 G.$E.conTnBottom.on('click', function(e) {
3170 G.GOM.displayedMoreSteps++;
3171 GalleryResize();
3172 });
3173 }
3174 break;
3175 case 'FULLCONTENT':
3176 default:
3177 break;
3178 }
3179 }
3180
3181
3182 // add one album/folder to the breadcrumb
3183 function breadcrumbAdd( albumIdx ) {
3184
3185 var ic='';
3186 if( !G.O.breadcrumbHideIcons ) {
3187 ic=G.O.icons.breadcrumbAlbum;
3188 if( albumIdx == 0 ) {
3189 ic=G.O.icons.breadcrumbHome;
3190 }
3191 }
3192 var $newDiv =jQuery('<div class="oneItem">'+ic + G.I[albumIdx].title+'</div>').appendTo(G.GOM.navigationBar.$newContent.find('.nGY2Breadcrumb'));
3193 if( G.O.breadcrumbOnlyCurrentLevel ) {
3194 // link to parent folder (only 1 level is displayed in the breadcrumb)
3195 if( albumIdx == 0 ) {
3196 // no parent level -> stay on current one
3197 jQuery($newDiv).data('albumID','0');
3198 }
3199 else {
3200 jQuery($newDiv).data('albumID',G.I[albumIdx].albumID);
3201 }
3202 }
3203 else {
3204 // link to current folder
3205 jQuery($newDiv).data('albumID',G.I[albumIdx].GetID());
3206 }
3207 $newDiv.click(function() {
3208 var cAlbumID = jQuery(this).data('albumID');
3209 DisplayAlbum('-1', cAlbumID);
3210 return;
3211 });
3212 }
3213
3214 // add one separator to breadcrumb
3215 function breadcrumbAddSeparator( lastAlbumID ) {
3216 var $newSep=jQuery('<div class="oneItem">'+(G.O.RTL ? G.O.icons.breadcrumbSeparatorRtl : G.O.icons.breadcrumbSeparator)+'</div>').appendTo(G.GOM.navigationBar.$newContent.find('.nGY2Breadcrumb'));
3217 jQuery($newSep).data('albumIdx',lastAlbumID);
3218 $newSep.click(function() {
3219 var sepAlbumIdx=jQuery(this).data('albumIdx');
3220 DisplayAlbum('-1', G.I[sepAlbumIdx].GetID());
3221 return;
3222 });
3223 }
3224
3225
3226
3227 // Manage the gallery toolbar (breadcrumb + tag filter + pagination next/previous)
3228 function GalleryNavigationBar( albumIdx ) {
3229
3230 // Title + background image
3231 // var bgImage='';
3232 // var l=G.I.length;
3233 // var albumID = G.I[albumIdx].GetID();
3234 // for( var idx=0; idx<l ; idx++) {
3235 // var item=G.I[idx];
3236 // if( item.kind == 'image' && item.isToDisplay(albumID) ) {
3237 // bgImage='<div id="pipo" class="pipo" style="height: 150px; width:100%; background-image: url("' + item.responsiveURL() + '"); background-size: cover; background-position: center center; filter:blur(2px)">pipo</div>';
3238 // break;
3239 // }
3240 // }
3241
3242 //console.log(bgImage);
3243
3244 // new navigation bar items are not build in the DOM, but in memory
3245 G.GOM.navigationBar.$newContent=jQuery('<div class="nGY2Navigationbar"></div>');
3246 //G.GOM.navigationBar.$newContent = jQuery(bgImage );
3247 //console.log(G.GOM.navigationBar.$newContent);
3248
3249 //-- manage breadcrumb
3250 if( G.O.displayBreadcrumb == true && !G.O.thumbnailAlbumDisplayImage) {
3251 // retrieve new folder level
3252 var newLevel = 0,
3253 lstItems=[];
3254 if( albumIdx != 0 ) {
3255 var l=G.I.length,
3256 parentID=0;
3257
3258 lstItems.push(albumIdx);
3259 var curIdx=albumIdx;
3260 newLevel++;
3261
3262 while( G.I[curIdx].albumID != 0 && G.I[curIdx].albumID != -1) {
3263 for(var i=1; i < l; i++ ) {
3264 if( G.I[i].GetID() == G.I[curIdx].albumID ) {
3265 curIdx=i;
3266 lstItems.push(curIdx);
3267 newLevel++;
3268 break;
3269 }
3270 }
3271 }
3272 }
3273
3274 // build breadcrumb
3275 if( !(G.O.breadcrumbAutoHideTopLevel && newLevel == 0) ) {
3276 BreadcrumbBuild( lstItems );
3277 }
3278 }
3279
3280
3281 //-- manage and build tag filters
3282 if( G.galleryFilterTags.Get() != false ) {
3283 var nTags = G.I[albumIdx].albumTagList.length;
3284 if( nTags > 0 ) {
3285 for(var i = 0; i < nTags; i++ ) {
3286 var s = G.I[albumIdx].albumTagList[i];
3287 var ic = G.O.icons.navigationFilterUnselected;
3288 var tagClass = 'Unselected';
3289 if( jQuery.inArray(s, G.I[albumIdx].albumTagListSel) >= 0 ) {
3290 tagClass = 'Selected';
3291 ic = G.O.icons.navigationFilterSelected;
3292 }
3293
3294 var $newTag = jQuery('<div class="nGY2NavigationbarItem nGY2NavFilter' + tagClass + '">'+ ic +' '+ s +'</div>').appendTo(G.GOM.navigationBar.$newContent);
3295
3296 $newTag.click(function() {
3297
3298 var $this = jQuery(this);
3299 var tag = $this.text().replace(/^\s*|\s*$/, ''); //trim trailing/leading whitespace
3300
3301 if( G.galleryFilterTagsMode.Get() == 'single' ) {
3302 // single TAG selection
3303 G.I[albumIdx].albumTagListSel = [];
3304 G.I[albumIdx].albumTagListSel.push(tag);
3305 }
3306 else {
3307 // multiple selection of TAGS
3308 // if( $this.hasClass('oneTagUnselected') ){
3309 if( $this.hasClass('nGY2NavFilterUnselected') ){
3310 G.I[albumIdx].albumTagListSel.push(tag);
3311 }
3312 else {
3313 var tidx=jQuery.inArray(tag,G.I[albumIdx].albumTagListSel);
3314 if( tidx != -1 ) {
3315 G.I[albumIdx].albumTagListSel.splice(tidx,1);
3316 }
3317 }
3318 $this.toggleClass('nGY2NavFilters-oneTagUnselected nGY2NavFilters-oneTagSelected');
3319 }
3320
3321 DisplayAlbum('-1', G.I[albumIdx].GetID());
3322 });
3323 }
3324
3325 // clear/reset TAGS selection
3326 var $newClearFilter=jQuery('<div class="nGY2NavigationbarItem nGY2NavFilterSelectAll">'+ G.O.icons.navigationFilterSelectedAll +'</div>').appendTo(G.GOM.navigationBar.$newContent);
3327 $newClearFilter.click(function() {
3328 // var nTags = G.I[albumIdx].albumTagList.length;
3329 G.I[albumIdx].albumTagListSel = [];
3330 // for(var i = 0; i < nTags; i++ ) {
3331 // var s = G.I[albumIdx].albumTagList[i];
3332 // G.I[albumIdx].albumTagListSel.push(s);
3333 // }
3334 DisplayAlbum('-1', G.I[albumIdx].GetID());
3335 });
3336 }
3337 }
3338
3339 // --- Gallery pagination next/previous
3340 if( G.galleryDisplayMode.Get() == "PAGINATION" && G.O.galleryPaginationTopButtons ) {
3341 if( G.layout.support.rows && G.galleryMaxRows.Get() > 0 ) {
3342 // ManagePagination( G.GOM.albumIdx );
3343 var $newTagPrev = jQuery('<div class="nGY2NavigationbarItem nGY2NavPagination">'+G.O.icons.navigationPaginationPrevious+'</div>').appendTo(G.GOM.navigationBar.$newContent);
3344 $newTagPrev.click(function() {
3345 paginationPreviousPage();
3346 });
3347 var $newTagNext = jQuery('<div class="nGY2NavigationbarItem nGY2NavPagination">'+G.O.icons.navigationPaginationNext+'</div>').appendTo(G.GOM.navigationBar.$newContent);
3348 $newTagNext.click(function() {
3349 paginationNextPage();
3350 });
3351 }
3352 }
3353
3354 }
3355
3356 function BreadcrumbBuild(lstItems) {
3357
3358 // console.log(G.GOM.navigationBar.$newContent);
3359 jQuery('<div class="nGY2NavigationbarItem nGY2Breadcrumb"></div>').appendTo(G.GOM.navigationBar.$newContent);
3360 // console.log(G.GOM.navigationBar.$newContent);
3361
3362 if( G.O.breadcrumbOnlyCurrentLevel ) {
3363 // display only 1 separator and the current folder level
3364 if( lstItems.length == 0 ) {
3365 breadcrumbAdd(0);
3366 }
3367 else {
3368 // var last=lstItems.length-1;
3369 if( lstItems.length == 1 ) {
3370 breadcrumbAddSeparator(0); // root level
3371 }
3372 else {
3373 breadcrumbAddSeparator(lstItems[0]);
3374 }
3375 breadcrumbAdd(lstItems[0]);
3376 }
3377 }
3378 else {
3379 // display the full breadcrum (full folder levels including root level)
3380 breadcrumbAdd(0);
3381 if( lstItems.length > 0 ) {
3382 breadcrumbAddSeparator(0);
3383 for(var i=lstItems.length-1; i>=0 ; i-- ) {
3384 breadcrumbAdd(lstItems[i]);
3385 if( i > 0 ) {
3386 breadcrumbAddSeparator(lstItems[i-1]);
3387 }
3388 }
3389 }
3390 }
3391
3392 }
3393
3394
3395 // Display gallery pagination
3396 function ManagePagination() {
3397
3398 G.$E.conTnBottom.css('opacity', 0);
3399 G.$E.conTnBottom.children().remove();
3400
3401 if( G.GOM.items.length == 0 ) { return; } // no thumbnail to display
3402
3403 // calculate the number of pages
3404 var nbPages = Math.ceil((G.GOM.items[G.GOM.items.length - 1].row + 1)/G.galleryMaxRows.Get());
3405
3406 // only one page -> do not display pagination
3407 if( nbPages == 1 ) { return; }
3408
3409 // check if current page still exists (for example after a resize)
3410 if( G.GOM.pagination.currentPage > (nbPages-1) ) {
3411 G.GOM.pagination.currentPage = nbPages-1;
3412 }
3413
3414 GalleryRenderGetInterval();
3415 // nothing to display --> exit
3416 if( G.GOM.displayInterval.len == 0 ) { return; }
3417
3418 // display "previous"
3419 if( G.O.galleryPaginationMode == 'NUMBERS' && G.GOM.pagination.currentPage > 0 ) {
3420 var $eltPrev = jQuery('<div class="nGY2PaginationPrev">'+G.O.icons.paginationPrevious+'</div>').appendTo(G.$E.conTnBottom);
3421 $eltPrev.click(function(e) {
3422 paginationPreviousPage();
3423 });
3424 }
3425
3426 var firstPage = 0;
3427 var lastPage = nbPages;
3428 if( G.O.galleryPaginationMode != 'NUMBERS' ) {
3429 // no 'previous'/'next' and no max number of pagination items
3430 firstPage = 0;
3431 }
3432 else {
3433 // display pagination numbers and previous/next
3434 // var vp = G.O.paginationVisiblePages;
3435 var numberOfPagesToDisplay = G.O.paginationVisiblePages;
3436 if( numberOfPagesToDisplay >= nbPages ) {
3437 firstPage = 0;
3438 }
3439 else {
3440 // we have more pages than we want to display
3441 var nbBeforeAfter = 0;
3442 if( isOdd(numberOfPagesToDisplay) ) {
3443 nbBeforeAfter = (numberOfPagesToDisplay + 1) / 2;
3444 }
3445 else {
3446 nbBeforeAfter = numberOfPagesToDisplay / 2;
3447 }
3448
3449 if( G.GOM.pagination.currentPage < nbBeforeAfter ) {
3450 firstPage = 0;
3451 lastPage = numberOfPagesToDisplay - 1;
3452 if( lastPage > nbPages ) {
3453 lastPage = nbPages - 1;
3454 }
3455 }
3456 else {
3457 firstPage = G.GOM.pagination.currentPage - nbBeforeAfter;
3458 lastPage = firstPage + numberOfPagesToDisplay;
3459 if( lastPage > nbPages ) {
3460 lastPage = nbPages - 1;
3461 }
3462 }
3463
3464 if( (lastPage - firstPage) < numberOfPagesToDisplay ) {
3465 firstPage = lastPage - numberOfPagesToDisplay;
3466 if( firstPage < 0 ) {
3467 firstPage = 0;
3468 }
3469 }
3470
3471 }
3472 }
3473
3474 // render pagination items
3475 for(var i = firstPage; i < lastPage; i++ ) {
3476 var c = '';
3477 var p = '';
3478
3479 switch( G.O.galleryPaginationMode ) {
3480 case 'NUMBERS':
3481 c = 'nGY2paginationItem';
3482 p = i + 1;
3483 break;
3484 case 'DOTS':
3485 c = 'nGY2paginationDot';
3486 break;
3487 case 'RECTANGLES':
3488 c = 'nGY2paginationRectangle';
3489 break;
3490 }
3491 if( i == G.GOM.pagination.currentPage ) {
3492 c += 'CurrentPage';
3493 }
3494
3495 var elt$ = jQuery('<div class="' + c + '">' + p + '</div>').appendTo(G.$E.conTnBottom);
3496 elt$.data('pageNumber', i );
3497 elt$.click( function(e) {
3498 G.GOM.pagination.currentPage = jQuery(this).data('pageNumber');
3499 TriggerCustomEvent('pageChanged');
3500
3501 // scroll to top of gallery if not displayed
3502 G.GOM.ScrollToTop();
3503
3504 GalleryDisplayPart1();
3505 GalleryDisplayPart2( true );
3506 });
3507
3508 }
3509
3510 // display "next"
3511 if( G.O.galleryPaginationMode == 'NUMBERS' && (G.GOM.pagination.currentPage + 1) < nbPages ) {
3512 var $eltNext = jQuery('<div class="nGY2PaginationNext">' + G.O.icons.paginationNext + '</div>').appendTo(G.$E.conTnBottom);
3513 $eltNext.click( function(e) {
3514 paginationNextPage();
3515 });
3516 }
3517
3518 G.$E.conTnBottom.css('opacity', 1);
3519
3520 }
3521 function isOdd(num) { return (num % 2) == 1;}
3522
3523 // pagination - next page
3524 function paginationNextPage() {
3525 // var aIdx = G.GOM.albumIdx;
3526 var n1 = 0;
3527 ThumbnailHoverOutAll();
3528
3529 // pagination - max lines per page mode
3530 if( G.galleryMaxRows.Get() > 0 ) {
3531 // number of pages
3532 n1 = (G.GOM.items[G.GOM.items.length - 1].row + 1) / G.galleryMaxRows.Get();
3533 }
3534 var n2 = Math.ceil(n1);
3535 var pn = G.GOM.pagination.currentPage;
3536 if( pn < (n2-1) ) {
3537 pn++;
3538 }
3539 else {
3540 pn = 0;
3541 }
3542
3543 G.GOM.pagination.currentPage = pn;
3544 TriggerCustomEvent('pageChanged');
3545
3546 // scroll to top of gallery if not displayed
3547 G.GOM.ScrollToTop();
3548
3549 GalleryDisplayPart1();
3550 GalleryDisplayPart2( true );
3551 }
3552
3553 // pagination - previous page
3554 function paginationPreviousPage() {
3555 // var aIdx=G.$E.conTnBottom.data('galleryIdx'),
3556 // var aIdx = G.GOM.albumIdx;
3557 var n1 = 0;
3558
3559 ThumbnailHoverOutAll();
3560
3561 // pagination - max lines per page mode
3562 if( G.galleryMaxRows.Get() > 0 ) {
3563 // number of pages
3564 n1 = (G.GOM.items[G.GOM.items.length - 1].row + 1) / G.galleryMaxRows.Get();
3565 }
3566 var n2 = Math.ceil(n1);
3567
3568 // var pn=G.$E.conTnBottom.data('currentPageNumber');
3569 var pn = G.GOM.pagination.currentPage;
3570 if( pn > 0 ) {
3571 pn--;
3572 }
3573 else {
3574 pn = n2 - 1;
3575 }
3576
3577 G.GOM.pagination.currentPage = pn;
3578 TriggerCustomEvent('pageChanged');
3579
3580 // scroll to top of gallery if not displayed
3581 G.GOM.ScrollToTop();
3582
3583 GalleryDisplayPart1();
3584 GalleryDisplayPart2( true );
3585 }
3586
3587 // retrieve the from/to intervall for gallery thumbnail render
3588 function GalleryRenderGetInterval() {
3589 G.GOM.displayInterval.from = 0;
3590 G.GOM.displayInterval.len = G.I.length;
3591
3592 switch( G.galleryDisplayMode.Get() ) {
3593 case 'PAGINATION':
3594 if( G.layout.support.rows ) {
3595 let nbTn = G.GOM.items.length;
3596 var firstRow = G.GOM.pagination.currentPage * G.galleryMaxRows.Get();
3597 var lastRow = firstRow + G.galleryMaxRows.Get();
3598 var firstTn = -1;
3599 G.GOM.displayInterval.len = 0;
3600 for( var i = 0; i < nbTn ; i++ ) {
3601 let curTn = G.GOM.items[i];
3602 if( curTn.row >= firstRow && curTn.row < lastRow ) {
3603 if( firstTn == -1 ) {
3604 G.GOM.displayInterval.from = i;
3605 firstTn = i;
3606 }
3607 G.GOM.displayInterval.len++;
3608 }
3609 }
3610 }
3611 break;
3612 case 'MOREBUTTON':
3613 if( G.layout.support.rows ) {
3614 let nbTn = G.GOM.items.length;
3615 let lastRow = G.O.galleryDisplayMoreStep * (G.GOM.displayedMoreSteps+1);
3616 G.GOM.displayInterval.len = 0;
3617 for( var i = 0; i < nbTn ; i++ ) {
3618 let curTn = G.GOM.items[i];
3619 if( curTn.row < lastRow ) {
3620 G.GOM.displayInterval.len++;
3621 }
3622 }
3623 }
3624 break;
3625 case 'ROWS':
3626 if( G.layout.support.rows ) {
3627 let nbTn = G.GOM.items.length;
3628 let lastRow = G.galleryMaxRows.Get();
3629 if( G.galleryLastRowFull.Get() && G.GOM.lastFullRow != -1 ) {
3630 if( lastRow > (G.GOM.lastFullRow + 1) ) {
3631 lastRow = G.GOM.lastFullRow + 1;
3632 }
3633 }
3634 G.GOM.displayInterval.len = 0;
3635 for( var i = 0; i < nbTn ; i++ ) {
3636 let curTn = G.GOM.items[i];
3637 if( curTn.row < lastRow ) {
3638 G.GOM.displayInterval.len++;
3639 }
3640 }
3641 }
3642 break;
3643 default:
3644 case 'FULLCONTENT':
3645 if( G.layout.support.rows && G.galleryLastRowFull.Get() && G.GOM.lastFullRow != -1 ) {
3646 let nbTn = G.GOM.items.length;
3647 let lastRow = G.GOM.lastFullRow + 1;
3648 G.GOM.displayInterval.len = 0;
3649 for( var i = 0; i < nbTn ; i++ ) {
3650 let curTn = G.GOM.items[i];
3651 if( curTn.row < lastRow ) {
3652 G.GOM.displayInterval.len++;
3653 }
3654 }
3655 }
3656 break;
3657 }
3658 }
3659
3660
3661 // RENDER THE GALLERY
3662 function GalleryRender( albumIdx ) {
3663 TriggerCustomEvent('galleryRenderStart');
3664 clearTimeout(G.GOM.slider.timerID);
3665 G.GOM.slider.hostIdx = -1; // disabled slider on thumbnail
3666
3667 var fu=G.O.fnGalleryRenderStart;
3668 if( fu !== null ) {
3669 // typeof fu == 'function' ? fu(albumIdx) : window[fu](albumIdx);
3670 typeof fu == 'function' ? fu( G.I[G.GOM.albumIdx] ) : window[fu]( G.I[G.GOM.albumIdx] );
3671 }
3672
3673 G.layout.SetEngine();
3674 G.galleryResizeEventEnabled = false;
3675 G.GOM.albumIdx = -1;
3676 G.GOM.lastDisplayedIdx = -1;
3677
3678 // pagination
3679 if( G.$E.conTnBottom !== undefined ) {
3680 // G.$E.conTnBottom.children().remove();
3681 G.$E.conTnBottom.empty();
3682 }
3683
3684 // navigation toolbar (breadcrumb + tag filters + pagination next/previous)
3685 GalleryNavigationBar(albumIdx);
3686
3687 if( G.GOM.firstDisplay ) {
3688 // first gallery display
3689 G.GOM.firstDisplay = false;
3690 var d = Date.now()-G.GOM.firstDisplayTime;
3691 if( d < G.O.galleryRenderDelay ) {
3692 // display after defined delay
3693 // setTimeout( function() { GalleryRenderPart1( albumIdx )}, G.O.galleryRenderDelay-d );
3694 requestTimeout( function() { GalleryRenderPart1( albumIdx )}, G.O.galleryRenderDelay-d );
3695 }
3696 else {
3697 GalleryRenderPart1( albumIdx );
3698 }
3699 G.O.galleryRenderDelay = 0;
3700
3701 }
3702 else {
3703 var hideNavigationBar = false;
3704 if( G.GOM.navigationBar.$newContent.children().length == 0 ) {
3705 hideNavigationBar = true;
3706 }
3707
3708 // hide everything
3709 var tweenable = new NGTweenable();
3710 tweenable.tween({
3711 from: { 'opacity': 1 },
3712 to: { 'opacity': 0 },
3713 duration: 300,
3714 easing: 'easeInQuart',
3715 attachment: { h: hideNavigationBar },
3716 step: function (state, att) {
3717 G.$E.conTnParent.css({'opacity': state.opacity });
3718 if( att.h ) {
3719 G.$E.conNavigationBar.css({ 'opacity': state.opacity });
3720 }
3721 },
3722 finish: function (state, att) {
3723 if( att.h ) {
3724 G.$E.conNavigationBar.css({ 'opacity': 0, 'display': 'none' });
3725 }
3726 // scroll to top of the gallery if needed
3727
3728 G.GOM.ScrollToTop();
3729
3730 GalleryRenderPart1( albumIdx );
3731 }
3732 });
3733 }
3734 }
3735
3736
3737 function GalleryRenderPart1( albumIdx ) {
3738 // display new navigation bar
3739 var oldN = G.$E.conNavigationBar.children().length;
3740 G.$E.conNavigationBar.empty();
3741 G.GOM.navigationBar.$newContent.children().clone(true,true).appendTo(G.$E.conNavigationBar);
3742 // G.GOM.navigationBar.$newContent.appendTo(G.$E.conNavigationBar);
3743 if( G.$E.conNavigationBar.children().length > 0 && oldN == 0 ) {
3744 G.$E.conNavigationBar.css({ 'opacity': 0, 'display': 'block' });
3745 var tweenable = new NGTweenable();
3746 tweenable.tween({
3747 from: { opacity: 0 },
3748 to: { opacity: 1 },
3749 duration: 200,
3750 easing: 'easeInQuart',
3751 step: function (state) {
3752 // window.ng_draf( function() {
3753 G.$E.conNavigationBar.css( state );
3754 // });
3755 },
3756 finish: function (state) {
3757 // window.ng_draf( function() {
3758 G.$E.conNavigationBar.css({ 'opacity': 1 });
3759 // display gallery
3760 // GalleryRenderPart2( albumIdx );
3761 // setTimeout(function(){ GalleryRenderPart2(albumIdx) }, 60);
3762 requestTimeout(function(){ GalleryRenderPart2(albumIdx) }, 20);
3763 // });
3764 }
3765 });
3766 }
3767 else {
3768 requestTimeout(function(){ GalleryRenderPart2(albumIdx) }, 20);
3769 }
3770
3771 }
3772
3773 // Gallery render part 2 -> remove all existing thumbnails
3774 function GalleryRenderPart2(albumIdx) {
3775
3776 G.GOM.lastZIndex = parseInt(G.$E.base.css('z-index'));
3777 if( isNaN(G.GOM.lastZIndex) ) {
3778 G.GOM.lastZIndex=0;
3779 }
3780 G.$E.conTnParent.css({ 'opacity': 0 });
3781 G.$E.conTn.off().empty();
3782 var l = G.I.length;
3783 for( var i = 0; i < l ; i++ ) {
3784 // reset each item
3785 var item = G.I[i];
3786 item.hovered = false;
3787 item.$elt = null;
3788 item.$Elts = [];
3789 item.eltTransform = [];
3790 item.eltFilter = [];
3791 item.width = 0;
3792 item.height = 0;
3793 item.left = 0;
3794 item.top = 0;
3795 item.resizedContentWidth = 0;
3796 item.resizedContentHeight = 0;
3797 item.thumbnailImgRevealed = false;
3798 }
3799
3800 if( G.CSStransformName == null ) {
3801 G.$E.conTn.css('left', '0px');
3802 }
3803 else {
3804 // G.$E.conTn.css( G.CSStransformName, 'translateX(0px)');
3805 G.$E.conTn.css( G.CSStransformName, 'none');
3806 }
3807
3808 // setTimeout(function(){ GalleryRenderPart3(albumIdx) }, 60);
3809 requestTimeout(function(){ GalleryRenderPart3(albumIdx) }, 20);
3810 // GalleryRenderPart3(albumIdx);
3811
3812 }
3813
3814 // Gallery render part 3 -> start building the new gallery
3815 function GalleryRenderPart3(albumIdx) {
3816 var d = new Date();
3817
3818 G.$E.conTnParent.css( 'opacity', 1);
3819
3820 G.GOM.items = [];
3821 G.GOM.displayedMoreSteps = 0;
3822 // retrieve label height
3823 if( G.O.thumbnailLabel.get('position') == 'onBottom' ) {
3824 // retrieve height each time because size can change depending on thumbnail's settings
3825 G.tn.labelHeight[G.GOM.curNavLevel] = ThumbnailGetLabelHeight();
3826 }
3827 else {
3828 G.tn.labelHeight[G.GOM.curNavLevel] = 0;
3829 }
3830 G.GOM.albumIdx=albumIdx;
3831
3832 TriggerCustomEvent('galleryRenderEnd');
3833 var fu=G.O.fnGalleryRenderEnd;
3834 if( fu !== null ) {
3835 // typeof fu == 'function' ? fu(albumIdx) : window[fu](albumIdx);
3836 typeof fu == 'function' ? fu(G.I[G.GOM.albumIdx] ) : window[fu](G.I[G.GOM.albumIdx] );
3837 }
3838
3839 // Step 1: populate GOM
3840 if( GalleryPopulateGOM() ) {
3841 // step 2: calculate layout
3842 GallerySetLayout();
3843
3844 // step 3: display whole gallery
3845 GalleryAppear();
3846
3847 // step 4: display thumbnails
3848 GalleryDisplayPart1();
3849 requestTimeout(function(){ GalleryDisplayPart2( false ) }, 20);
3850 }
3851 else {
3852 //
3853 G.galleryResizeEventEnabled = true;
3854 }
3855
3856 if( G.O.debugMode ) { console.log('GalleryRenderPart3: '+ (new Date()-d)); }
3857
3858 }
3859
3860
3861 // Resize the gallery
3862 function GalleryResize() {
3863 var d = new Date();
3864 G.galleryResizeEventEnabled = false;
3865 // G.GOM.cache.areaWidth=G.$E.conTnParent.width();
3866 if( GallerySetLayout() == false ) {
3867 G.galleryResizeEventEnabled = true;
3868 if( G.O.debugMode ) { console.log('GalleryResize1: '+ (new Date()-d)); }
3869 return;
3870 }
3871 if( G.O.debugMode ) { console.log('GalleryResizeSetLayout: '+ (new Date()-d)); }
3872
3873 GalleryDisplayPart1();
3874 GalleryDisplayPart2( false );
3875
3876 if( G.O.debugMode ) { console.log('GalleryResizeFull: '+ (new Date()-d)); }
3877 }
3878
3879
3880
3881 // copy items (album content) to GOM
3882 // returns:
3883 // true: thumbnail image size is needed for the layout, but not set -> retrieve the sizes and display gallery
3884 function GalleryPopulateGOM() {
3885
3886 var preloadImages = '';
3887 var albumID = G.I[G.GOM.albumIdx].GetID();
3888 var l = G.I.length;
3889 var cnt = 0;
3890
3891 for( var idx = 0; idx < l; idx++ ) {
3892 var item = G.I[idx];
3893 // check album
3894 if( item.isToDisplay(albumID) ) {
3895 var w = item.thumbImg().width;
3896 var h = item.thumbImg().height;
3897 // if unknown image size and layout is not grid --> we need to retrieve the size of the images
3898 if( G.layout.prerequisite.imageSize && ( w == 0 || h == 0) ) {
3899 // if( true ) {
3900 preloadImages += '<img src="'+item.thumbImg().src+'" data-idx="'+cnt+'" data-albumidx="'+G.GOM.albumIdx+'">';
3901 }
3902
3903 // set default size if required
3904 if( h == 0 ) {
3905 h = G.tn.defaultSize.getHeight();
3906 }
3907 if( w == 0 ) {
3908 w = G.tn.defaultSize.getWidth();
3909 }
3910 var tn = new G.GOM.GTn(idx, w, h);
3911 G.GOM.items.push(tn);
3912 cnt++;
3913 }
3914 }
3915
3916 TriggerCustomEvent('galleryObjectModelBuilt');
3917 var fu = G.O.fnGalleryObjectModelBuilt;
3918 if( fu !== null ) {
3919 typeof fu == 'function' ? fu() : window[fu]();
3920 }
3921
3922 if( preloadImages != '' ) {
3923 // preload images to retrieve their size and then resize the gallery (=GallerySetLayout()+ GalleryDisplay())
3924 var $newImg = jQuery(preloadImages);
3925 var gi_imgLoad = ngimagesLoaded( $newImg );
3926 $newImg = null;
3927 gi_imgLoad.on( 'progress', function( instance, image ) {
3928
3929 if( image.isLoaded ) {
3930 var idx = image.img.getAttribute('data-idx');
3931 var albumIdx = image.img.getAttribute('data-albumidx');
3932 if( albumIdx == G.GOM.albumIdx ) {
3933 // ignore event if not on current album
3934 var curTn = G.GOM.items[idx];
3935 curTn.imageWidth = image.img.naturalWidth;
3936 curTn.imageHeight = image.img.naturalHeight;
3937 var item = G.I[curTn.thumbnailIdx];
3938 item.thumbs.width[G.GOM.curNavLevel][G.GOM.curWidth] = curTn.imageWidth;
3939 item.thumbs.height[G.GOM.curNavLevel][G.GOM.curWidth] = curTn.imageHeight;
3940
3941 // resize the gallery
3942 G.GalleryResizeThrottled();
3943
3944 // set the retrieved size to all levels with same configuration
3945 var object = item.thumbs.width.l1;
3946 for (let property in object) {
3947 if (object.hasOwnProperty(property)) {
3948 if( property != G.GOM.curWidth ) {
3949 if( G.tn.settings.width.l1[property] == G.tn.settings.getW() && G.tn.settings.height.l1[property] == G.tn.settings.getH() ) {
3950 item.thumbs.width.l1[property] = curTn.imageWidth;
3951 item.thumbs.height.l1[property] = curTn.imageHeight;
3952 }
3953 }
3954 }
3955 }
3956 object = item.thumbs.width.lN;
3957 for (let property in object) {
3958 if (object.hasOwnProperty(property)) {
3959 if( property != G.GOM.curWidth ) {
3960 if( G.tn.settings.width.lN[property] == G.tn.settings.getW() && G.tn.settings.height.lN[property] == G.tn.settings.getH() ) {
3961 item.thumbs.width.lN[property] = curTn.imageWidth;
3962 item.thumbs.height.lN[property] = curTn.imageHeight;
3963 }
3964 }
3965 }
3966 }
3967 }
3968 }
3969 });
3970 G.galleryResizeEventEnabled = true;
3971 return false;
3972 }
3973 else {
3974 return true;
3975 }
3976
3977 }
3978
3979 //----- Calculate the layout of the thumbnails for the full gallery
3980 function GallerySetLayout() {
3981 var r = true;
3982 // width of the available area
3983 G.GOM.cache.areaWidth = G.$E.conTnParent.width();
3984 G.GOM.displayArea = { width:0, height:0 };
3985
3986 switch( G.layout.engine ) {
3987 case 'JUSTIFIED':
3988 r = GallerySetLayoutWidthtAuto();
3989 break;
3990 case 'CASCADING':
3991 r = GallerySetLayoutHeightAuto();
3992 break;
3993 case 'MOSAIC':
3994 r = GallerySetLayoutMosaic();
3995 break;
3996 case 'GRID':
3997 default:
3998 r = GallerySetLayoutGrid();
3999 break;
4000 }
4001
4002 TriggerCustomEvent('galleryLayoutApplied');
4003 var fu = G.O.fnGalleryLayoutApplied;
4004 if( fu !== null ) {
4005 typeof fu == 'function' ? fu() : window[fu]();
4006 }
4007 return r;
4008
4009 }
4010
4011
4012 //----- CASCADING LAYOUT
4013 function GallerySetLayoutHeightAuto() {
4014 var curCol = 0,
4015 areaWidth = G.GOM.cache.areaWidth,
4016 curRow = 0,
4017 colHeight = [],
4018 maxCol = NbThumbnailsPerRow(areaWidth),
4019 gutterWidth = 0,
4020 gutterHeight = G.tn.settings.GetResponsive('gutterHeight');
4021 var w = 0;
4022 var scaleFactor = 1;
4023 var tnWidth = G.tn.defaultSize.getOuterWidth();
4024 var nbTn = G.GOM.items.length;
4025 var curPosY = 0;
4026
4027 if( G.O.thumbnailAlignment == 'justified' ) {
4028 maxCol = Math.min(maxCol, nbTn);
4029 gutterWidth = ( maxCol == 1 ? 0 : (areaWidth - (maxCol * tnWidth) ) / (maxCol - 1) );
4030 }
4031 else {
4032 gutterWidth = G.tn.settings.GetResponsive('gutterWidth');
4033 }
4034
4035
4036 var borderWidth = G.tn.opt.Get('borderHorizontal') * 2;
4037 var borderHeight = G.tn.opt.Get('borderVertical') * 2;
4038
4039 G.GOM.lastFullRow=-1; // feature disabled
4040
4041 // Retrieve the real used width of the area (the evaluation is based on the content of the first line)
4042 if( G.O.thumbnailAlignment == 'fillWidth' ) {
4043 // fillWidth --> evaluate scale factor and number of columns
4044 var totalGutterWidth = (maxCol - 1) * gutterWidth;
4045 scaleFactor = (areaWidth - totalGutterWidth) / (maxCol * tnWidth);
4046 if( scaleFactor > 1 ) {
4047 maxCol++; // add one column and re-evaluate the scale factor
4048 }
4049 totalGutterWidth = (maxCol - 1) * gutterWidth;
4050 scaleFactor = Math.min( (areaWidth - totalGutterWidth) / (maxCol*tnWidth), 1); // no upscale
4051 }
4052
4053
4054 tnWidth = Math.round( tnWidth * scaleFactor);
4055 var contentWidth = tnWidth - borderWidth;
4056
4057 // loop to position the thumbnails, and set their size
4058 var baseHeight = Math.round( G.tn.opt.Get('baseGridHeight') * scaleFactor );
4059 for( var i = 0; i < nbTn ; i++ ) {
4060 var curTn = G.GOM.items[i];
4061 if( curTn.deleted == true ) { break; } // item is logically deleted
4062 if( curTn.imageHeight > 0 && curTn.imageWidth > 0 ) {
4063 var curPosX = 0,
4064 curPosY = 0;
4065 var imageRatio = curTn.imageHeight / curTn.imageWidth;
4066 // curTn.resizedContentWidth = tnWidth - borderWidth;
4067 curTn.resizedContentWidth = contentWidth;
4068 curTn.resizedContentHeight = curTn.resizedContentWidth * imageRatio;
4069 if( baseHeight > 0 ) {
4070 // grid based vertical position
4071 var t = Math.max( Math.trunc(curTn.resizedContentHeight/baseHeight), 1) ;
4072 curTn.resizedContentHeight = baseHeight * t + ((t-1)*(borderHeight+gutterHeight));
4073 }
4074
4075 curTn.height = curTn.resizedContentHeight + borderHeight + G.tn.labelHeight.get();
4076 curTn.width = tnWidth;
4077 curTn.row = 0;
4078
4079 if( curRow == 0 ) {
4080 // first row
4081 curPosX = curCol * (tnWidth + gutterWidth);
4082 colHeight[curCol] = curTn.height + gutterHeight;
4083
4084 curCol++;
4085 if( curCol >= maxCol ) {
4086 curCol = 0;
4087 curRow++;
4088 }
4089 }
4090 else {
4091 var c=0,
4092 minColHeight=colHeight[0];
4093 for( var j = 1; j < maxCol; j++) {
4094 if( (colHeight[j] + 5) < minColHeight ) { // +5 --> threshold
4095 minColHeight = colHeight[j];
4096 c = j;
4097 //break;
4098 }
4099 }
4100 curPosY = colHeight[c];
4101 curPosX = c * (tnWidth + gutterWidth);
4102 colHeight[c] = curPosY + curTn.height + gutterHeight;
4103 }
4104
4105 var x = curPosX;
4106 if( G.O.RTL) {
4107 x= w - curPosX - tnWidth;
4108 }
4109
4110 curTn.left = x;
4111 curTn.top = curPosY;
4112 }
4113 }
4114
4115 G.GOM.displayArea.width= maxCol * (tnWidth + gutterWidth) - gutterWidth;
4116 return true;
4117 }
4118
4119
4120 //----- JUSTIFIED LAYOUT
4121 function GallerySetLayoutWidthtAuto() {
4122 var curWidth = 0,
4123 areaWidth = G.GOM.cache.areaWidth,
4124 lastPosX = 0,
4125 curPosY = 0,
4126 rowLastItem = [],
4127 rowNum = 0,
4128 rowHeight = [],
4129 bNewRow = false,
4130 cnt = 0,
4131 gutterWidth = G.tn.settings.GetResponsive('gutterWidth'),
4132 gutterHeight = G.tn.settings.GetResponsive('gutterHeight');
4133 // by grief-of-these-days
4134 var maxRowHeightVertical = 0; // max height of a row with vertical thumbs
4135 var maxRowHeightHorizontal = 0; // max height of a row with horizontal thumbs
4136 var rowHasVertical = false; // current row has vertical thumbs
4137 var rowHasHorizontal = false; // current row has horizontal thumbs
4138
4139 var tnHeight = G.tn.defaultSize.getOuterHeight();
4140 var borderWidth = G.tn.opt.Get('borderHorizontal') * 2;
4141 var borderHeight = G.tn.opt.Get('borderVertical') * 2;
4142 var nbTnInCurrRow = 1;
4143 var nbTn = G.GOM.items.length;
4144
4145 // first loop --> retrieve each row image height
4146 for( var i = 0; i < nbTn ; i++ ) {
4147 let curTn = G.GOM.items[i];
4148 if( curTn.deleted == true ) { break; } // item is logically deleted
4149 if( curTn.imageWidth > 0 ) {
4150 let imageRatio = curTn.imageWidth / curTn.imageHeight;
4151 let imageWidth = Math.floor( tnHeight * imageRatio );
4152
4153 if( bNewRow ) {
4154 bNewRow = false;
4155 rowNum++;
4156 curWidth = 0;
4157 rowHasVertical = false;
4158 rowHasHorizontal = false;
4159 nbTnInCurrRow = 1;
4160 }
4161 // by grief-of-these-days
4162 if( curTn.imageHeight > curTn.imageWidth ) {
4163 rowHasVertical = true;
4164 }
4165 else {
4166 rowHasHorizontal = true;
4167 }
4168
4169 if( (curWidth + gutterWidth + imageWidth) < (areaWidth - (nbTnInCurrRow * borderWidth)) ) {
4170 // enough place left in the current row
4171 curWidth += imageWidth + gutterWidth;
4172 rowHeight[rowNum] = tnHeight;
4173
4174 // prevent incomplete row from being heigher than the previous ones.
4175 // by grief-of-these-days
4176 var rowHeightLimit = Math.max(rowHasVertical ? maxRowHeightVertical : 0, rowHasHorizontal ? maxRowHeightHorizontal : 0);
4177 if( rowHeightLimit > 0 ) {
4178 rowHeight[rowNum] = Math.min(rowHeight[rowNum], rowHeightLimit);
4179 }
4180
4181 rowLastItem[rowNum] = i;
4182 }
4183 else {
4184 // new row after current item --> we need to adujet the row height to have enough space for the current thumbnail
4185 curWidth += gutterWidth+imageWidth;
4186 let ratio = (areaWidth - nbTnInCurrRow * borderWidth) / curWidth;
4187 let rH = Math.floor(tnHeight * ratio);
4188 rowHeight[rowNum] = rH;
4189
4190 // save the max row height for each thumb orientation.
4191 // by grief-of-these-days
4192 if( rowHasVertical ) {
4193 maxRowHeightVertical = Math.max( maxRowHeightVertical, rH );
4194 }
4195 if( rowHasHorizontal ) {
4196 maxRowHeightHorizontal = Math.max( maxRowHeightHorizontal, rH );
4197 }
4198
4199 rowLastItem[rowNum] = i;
4200 bNewRow = true;
4201 }
4202 cnt++;
4203 nbTnInCurrRow++;
4204 }
4205 }
4206
4207 rowNum = 0;
4208 curPosY = 0;
4209 lastPosX = 0;
4210 cnt = 0;
4211
4212 G.GOM.lastFullRow = 0; // display at leat 1 row (even if not full)
4213
4214 // second loop --> calculate each thumbnail size
4215 for( var i = 0; i < nbTn ; i++ ) {
4216 let curTn = G.GOM.items[i];
4217 if( curTn.imageWidth > 0 ) {
4218 let imageRatio = curTn.imageWidth / curTn.imageHeight;
4219 let imageWidth = Math.floor( imageRatio * rowHeight[rowNum] ); // border is already NOT included
4220
4221 if( i == rowLastItem[rowNum] ) {
4222 // row last item --> adjust image width because of rounding problems
4223 if( rowLastItem.length != (rowNum+1) ) {
4224 // last item in current row -> use the full remaining width
4225 imageWidth = areaWidth - lastPosX - borderWidth;
4226 }
4227 else {
4228 // very last item (on the last row)
4229 if( (lastPosX + gutterWidth + imageWidth + borderWidth ) > areaWidth ) {
4230 // reduce size if image is wider as the remaining space
4231 imageWidth = areaWidth - lastPosX - borderWidth;
4232 }
4233 }
4234 }
4235
4236 let rh = parseInt( rowHeight[rowNum] );
4237 imageWidth = parseInt( imageWidth );
4238
4239 // thumbnail image size
4240 curTn.resizedContentWidth = imageWidth;
4241 curTn.resizedContentHeight = rh;
4242 // thumbnail position and size
4243 curTn.width = imageWidth + borderWidth;
4244 curTn.height= rh + G.tn.labelHeight.get() + borderHeight;
4245 curTn.row = rowNum;
4246
4247 curTn.top = curPosY;
4248 let x = lastPosX;
4249 if( G.O.RTL) {
4250 x = areaWidth - lastPosX - curTn.width ;
4251 }
4252 curTn.left = x;
4253
4254 lastPosX += curTn.width + gutterWidth;
4255
4256 if( i == rowLastItem[rowNum] ) {
4257 // start a new row
4258 curPosY += curTn.height + gutterHeight;
4259 G.GOM.lastFullRow = rowNum - 1;
4260 rowNum++;
4261 lastPosX = 0;
4262 }
4263 cnt++;
4264 }
4265 else {
4266 return false;
4267 }
4268 }
4269
4270 // hover effect on gallery (vs on thumbnail) --> experimental / not used
4271 if( false ) {
4272 var newTop = 0;
4273 if( typeof GOMidx !== 'undefined' ) {
4274 if( G.GOM.albumIdx != -1 ) {
4275 var hoveredTn = G.GOM.items[GOMidx];
4276
4277 // hovered thumbnail
4278 hoveredTn.width += 40;
4279 hoveredTn.height += 40;
4280
4281 for( var i = 0; i < nbTn ; i++ ) {
4282 var curTn = G.GOM.items[i];
4283 if( curTn.imageWidth > 0 ) {
4284 if( curTn.row == hoveredTn.row ) {
4285 // hovered row
4286 newTop = 40;
4287 if( hoveredTn.thumbnailIdx != curTn.thumbnailIdx ) {
4288 // not hovered thumbnail
4289 curTn.top += 30;
4290 curTn.width -= 20;
4291 curTn.height -= 20;
4292 }
4293 }
4294 else {
4295 // not hovered row
4296 if( curTn.row == 0 ) {
4297 // first row
4298 }
4299 else {
4300 curTn.top += newTop;
4301 }
4302 }
4303 }
4304 }
4305 }
4306 }
4307 }
4308
4309 G.GOM.displayArea.width = areaWidth;
4310 return true;
4311 }
4312
4313
4314 //----- MOSAIC LAYOUT
4315 // Grid using a user defined pattern layout
4316 // With this layout, a pattern definition is handeld a row
4317 function GallerySetLayoutMosaic() {
4318 var areaWidth = G.GOM.cache.areaWidth;
4319 var gutterHeight = G.tn.settings.GetResponsive('gutterHeight');
4320 var gutterWidth = G.tn.settings.GetResponsive('gutterWidth');
4321 var borderWidth = G.tn.opt.Get('borderHorizontal') * 2;
4322 var borderHeight = G.tn.opt.Get('borderVertical') * 2;
4323
4324 var nbTn = G.GOM.items.length;
4325 var row = 0;
4326 var h = 0;
4327 var n = 0;
4328
4329
4330 // first loop: evaluate the gallery width based on the first row
4331 var nbCols = 0;
4332 var maxW = 0;
4333 let mosaicPattern = G.tn.settings.getMosaic();
4334 for( var i = 0; i < nbTn ; i++ ) {
4335 let curPatternElt = mosaicPattern[n];
4336
4337 var cLeft = (curPatternElt.c - 1) * G.tn.defaultSize.getOuterWidth() + (curPatternElt.c - 1) * gutterWidth;
4338 var cWidth = curPatternElt.w * G.tn.defaultSize.getOuterWidth() + (curPatternElt.w - 1) * gutterWidth;
4339
4340 maxW = Math.max(maxW, cLeft + cWidth );
4341
4342 nbCols = Math.max(nbCols, (curPatternElt.c - 1) + curPatternElt.w );
4343
4344 n++;
4345 if( n >= mosaicPattern.length ) {
4346 // end of pattern
4347 break;
4348 }
4349 }
4350 var totalGutterWidth = (nbCols - 1) * gutterWidth;
4351 var scaleFactor = Math.min( (areaWidth - totalGutterWidth ) / ( maxW - totalGutterWidth ), 1);
4352
4353 // second loop: position all the thumbnails based on the layout pattern
4354 row = 0;
4355 n = 0;
4356 // let mosaicPattern = G.tn.settings.getMosaic();
4357 for( var i = 0; i < nbTn ; i++ ) {
4358 let curTn = G.GOM.items[i];
4359 let curPatternElt = mosaicPattern[n];
4360
4361 curTn.top = Math.round((curPatternElt.r - 1) * G.tn.defaultSize.getOuterHeight()*scaleFactor) + (curPatternElt.r - 1) * gutterHeight + row * h + (G.tn.labelHeight.get()*(curPatternElt.r-1)) ;
4362 if( row > 0 ) {
4363 curTn.top += gutterHeight;
4364 }
4365
4366 curTn.left = (curPatternElt.c - 1) * Math.round(G.tn.defaultSize.getOuterWidth()*scaleFactor) + (curPatternElt.c - 1) * gutterWidth;
4367
4368 curTn.height = Math.round(curPatternElt.h * G.tn.defaultSize.getOuterHeight() * scaleFactor) + (curPatternElt.h - 1) * gutterHeight + (G.tn.labelHeight.get() * curPatternElt.h);
4369 curTn.resizedContentHeight = curTn.height - G.tn.labelHeight.get() - borderHeight;
4370
4371 curTn.width = Math.round(curPatternElt.w * G.tn.defaultSize.getOuterWidth()*scaleFactor) + (curPatternElt.w - 1) * gutterWidth;
4372 curTn.resizedContentWidth = curTn.width - borderWidth ;
4373
4374 curTn.row = row;
4375 if( row == 0 ) {
4376 h=Math.max(h, curTn.top + curTn.height);
4377 }
4378
4379 n++;
4380 if( n >= mosaicPattern.length ) {
4381 // end pattern -> new line
4382 n = 0;
4383 row++;
4384 }
4385 }
4386
4387 G.GOM.displayArea.width = (maxW - totalGutterWidth) * scaleFactor + totalGutterWidth;
4388 return true;
4389 }
4390
4391
4392
4393 // --- GRID LAYOUT
4394 function GallerySetLayoutGrid() {
4395 var curPosX= 0,
4396 curPosY= 0,
4397 areaWidth= G.GOM.cache.areaWidth,
4398 gutterWidth= 0,
4399 gutterHeight= G.tn.settings.GetResponsive('gutterHeight'),
4400 maxCol= NbThumbnailsPerRow(areaWidth),
4401 w= 0,
4402 cols= [],
4403 curCol= 0,
4404 newAreaWidth = areaWidth,
4405 tnWidth= G.tn.defaultSize.getOuterWidth();
4406 var scaleFactor = 1;
4407 var nbTn= G.GOM.items.length;
4408 var borderWidth = G.tn.opt.Get('borderHorizontal') * 2;
4409 var borderHeight = G.tn.opt.Get('borderVertical') * 2;
4410
4411 // retrieve gutter width
4412 if( G.O.thumbnailAlignment == 'justified' ) {
4413 maxCol = Math.min( maxCol, nbTn);
4414 gutterWidth = (maxCol==1 ? 0 : (areaWidth-(maxCol*tnWidth))/(maxCol-1));
4415 }
4416 else {
4417 gutterWidth = G.tn.settings.GetResponsive('gutterWidth');
4418 }
4419
4420 // first loop to retrieve the real used width of the area (the evaluation is based on the content of the first line)
4421 // Retrieve the real used width of the area (the evaluation is based on the content of the first line)
4422 if( G.O.RTL || G.O.thumbnailAlignment == 'fillWidth' ) {
4423 // scaled --> evaluate scale factor and number of columns
4424 var totalGutterWidth = (maxCol-1) * gutterWidth;
4425 scaleFactor = (areaWidth - totalGutterWidth) / (maxCol*tnWidth);
4426 if( scaleFactor > 1 ) {
4427 maxCol++; // add one column and re-evaluate the scale factor
4428 }
4429 totalGutterWidth = (maxCol-1) * gutterWidth;
4430 scaleFactor = Math.min( (areaWidth - totalGutterWidth) / (maxCol*tnWidth), 1); // no upscale
4431 newAreaWidth = (maxCol*tnWidth) + totalGutterWidth;
4432 }
4433
4434
4435 G.GOM.lastFullRow = 0 ; // display at leat 1 row (even if not full)
4436 // var lastPosY = 0;
4437 var row = 0;
4438
4439 tnWidth = Math.round(tnWidth * scaleFactor);
4440 var contentWidth = tnWidth - borderWidth;
4441 var tnHeight = Math.round(G.tn.defaultSize.getOuterHeight() * scaleFactor) + G.tn.labelHeight.get();
4442 var contentHeight = Math.round( G.tn.defaultSize.getOuterHeight() * scaleFactor) - borderHeight;
4443
4444 // loop to position and to set size of all thumbnails
4445 for( var i = 0; i < nbTn ; i++ ) {
4446 if( curPosY == 0 ) {
4447 curPosX = curCol * (tnWidth + gutterWidth)
4448 cols[curCol] = curPosX;
4449 w = curPosX + tnWidth;
4450 }
4451 else {
4452 curPosX = cols[curCol];
4453 }
4454
4455 var x = curPosX;
4456 if( G.O.RTL ) {
4457 x = parseInt(newAreaWidth) - curPosX - tnWidth;
4458 }
4459
4460 // MANDATORY : set thumbnail position AND size
4461 var curTn=G.GOM.items[i];
4462 curTn.top = curPosY;
4463 curTn.left = x;
4464 curTn.height = tnHeight;
4465 curTn.width = tnWidth;
4466 // image size
4467 if( G.O.thumbnailAlignment == 'fillWidth' ) {
4468 curTn.resizedContentWidth = contentWidth;
4469 curTn.resizedContentHeight = contentHeight;
4470 }
4471 curTn.row = row;
4472 // lastPosY = curPosY;
4473
4474 curCol++;
4475 if( curCol >= maxCol ){
4476 // new line
4477 curCol = 0;
4478 curPosY += tnHeight + gutterHeight;
4479 G.GOM.lastFullRow = row;
4480 row++;
4481 }
4482 }
4483 G.GOM.displayArea.width = w;
4484
4485 return true;
4486 }
4487
4488
4489
4490
4491 //----- Display the thumbnails according to the calculated layout
4492 function GalleryDisplayPart1() {
4493 if( G.CSStransformName == null ) {
4494 G.$E.conTn.css( 'left' , '0px');
4495 }
4496 else {
4497 G.$E.conTn.css( G.CSStransformName , 'none');
4498 }
4499 // CacheViewport();
4500 }
4501
4502 function CacheViewport() {
4503 G.GOM.cache.viewport = getViewport();
4504 // G.GOM.cache.areaWidth = G.$E.conTnParent.width();
4505 G.GOM.cache.areaWidth = G.$E.base.width();
4506
4507 // position of the gallery container
4508 // we use the position of the loadingbar because :
4509 // - the gallery may be wrong positioned due to one display animation currently running
4510 // - the loadingbar is never animated and positioned just before the gallery container
4511 //G.GOM.cache.containerOffset = G.$E.conTnParent.offset();
4512 if( !G.O.lightboxStandalone ) {
4513 G.GOM.cache.containerOffset = G.$E.conLoadingB.offset();
4514 }
4515 }
4516
4517
4518
4519 function GalleryDisplayPart2( forceTransition ) {
4520 CacheViewport();
4521
4522 var nbTn = G.GOM.items.length;
4523 G.GOM.itemsDisplayed = 0;
4524 var threshold = 50;
4525 var cnt = 0; // counter for delay between each thumbnail display
4526
4527
4528 GalleryRenderGetInterval();
4529
4530 for( var i = 0; i < nbTn ; i++ ) {
4531 let curTn = G.GOM.items[i];
4532 if( i >= G.GOM.displayInterval.from && cnt < G.GOM.displayInterval.len ) {
4533 curTn.inDisplayArea = true;
4534 if( forceTransition ) {
4535 curTn.neverDisplayed = true;
4536 }
4537 G.GOM.itemsDisplayed++;
4538 cnt++;
4539 }
4540 else{
4541 curTn.inDisplayArea = false;
4542 }
4543 }
4544
4545 // bottom of the gallery (pagination, more button...)
4546 GalleryBottomManage();
4547
4548 var tnToDisplay = [];
4549 var tnToReDisplay = [];
4550
4551 CacheViewport();
4552 G.GOM.clipArea.top = -1;
4553 cnt = 0 ;
4554 var lastTnIdx = -1;
4555 G.GOM.clipArea.height = 0;
4556 // NOTE: loop always the whole GOM.items --> in case an already displayed thumbnail needs to be removed
4557 for( var i = 0; i < nbTn ; i++ ) {
4558 let curTn = G.GOM.items[i];
4559 if( curTn.inDisplayArea ) {
4560 if( G.GOM.clipArea.top == -1 ) {
4561 G.GOM.clipArea.top = curTn.top;
4562 }
4563 if( (curTn.top - G.GOM.clipArea.top) <= -1 ) {
4564 // with mosaic layout, the first thumbnail may not give the top position
4565 G.GOM.clipArea.top = curTn.top;
4566 }
4567
4568 G.GOM.clipArea.height = Math.max( G.GOM.clipArea.height, curTn.top-G.GOM.clipArea.top + curTn.height);
4569
4570 if( curTn.neverDisplayed ) {
4571 // thumbnail is not displayed -> check if in viewport to display or not
4572 var top = G.GOM.cache.containerOffset.top + (curTn.top - G.GOM.clipArea.top);
4573 // var left=containerOffset.left+curTn.left;
4574 if( (top + curTn.height) >= (G.GOM.cache.viewport.t - threshold) && top <= (G.GOM.cache.viewport.t + G.GOM.cache.viewport.h + threshold) ) {
4575 // build thumbnail
4576 let item = G.I[curTn.thumbnailIdx];
4577 if( item.$elt == null ) {
4578 // ThumbnailBuild( item, curTn.thumbnailIdx, i, (i+1) == nbTn );
4579 ThumbnailBuild( item, curTn.thumbnailIdx, i );
4580 }
4581 tnToDisplay.push({idx:i, delay:cnt, top: curTn.top, left: curTn.left});
4582 cnt++;
4583 }
4584 }
4585 else {
4586 tnToReDisplay.push({idx: i, delay: 0, top: curTn.top, left: curTn.left});
4587 }
4588 // G.GOM.itemsDisplayed++;
4589 lastTnIdx = i;
4590 }
4591 else {
4592 curTn.displayed = false;
4593 let item = G.I[curTn.thumbnailIdx];
4594 if( item.$elt != null ){
4595 item.$elt.css({ opacity: 0, display: 'none' });
4596 }
4597 }
4598 }
4599
4600 var areaWidth = G.$E.conTnParent.width();
4601
4602 // set gallery area really used size
4603 // if( G.GOM.displayArea.width != G.GOM.displayAreaLast.width || G.GOM.displayArea.height != G.GOM.displayAreaLast.height ) {
4604 if( G.GOM.displayArea.width != G.GOM.displayAreaLast.width || G.GOM.clipArea.height != G.GOM.displayAreaLast.height ) {
4605 G.$E.conTn.width( G.GOM.displayArea.width ).height( G.GOM.clipArea.height );
4606 G.GOM.displayAreaLast.width = G.GOM.displayArea.width;
4607 G.GOM.displayAreaLast.height = G.GOM.clipArea.height;
4608 // G.GOM.displayAreaLast.height=G.GOM.displayArea.height-G.GOM.clipArea.top;
4609 }
4610
4611 if( areaWidth != G.$E.conTnParent.width() ) {
4612 // gallery area width changed since layout calculation (for example when a scrollbar appeared)
4613 // so we need re-calculate the layout before displaying the thumbnails
4614 G.GOM.cache.areaWidth = G.$E.conTnParent.width();
4615 GallerySetLayout();
4616 GalleryDisplayPart1();
4617 GalleryDisplayPart2( forceTransition );
4618 return;
4619 }
4620
4621 // counter of not displayed images (is displayed on the last thumbnail)
4622 if( G.layout.support.rows ) {
4623 if( G.galleryDisplayMode.Get() == 'ROWS' || (G.galleryDisplayMode.Get() == 'FULLCONTENT' && G.galleryLastRowFull.Get() && G.GOM.lastFullRow != -1) ){
4624 if( lastTnIdx < (nbTn - 1) ) {
4625 G.GOM.lastDisplayedIdxNew = lastTnIdx;
4626 }
4627 else {
4628 G.GOM.lastDisplayedIdxNew =- 1;
4629 }
4630 // remove last displayed counter
4631 if( G.GOM.lastDisplayedIdx != -1 ) {
4632 let item = G.I[G.GOM.items[G.GOM.lastDisplayedIdx].thumbnailIdx];
4633 item.$getElt('.nGY2GThumbnailIconsFullThumbnail').html('');
4634 }
4635 }
4636 }
4637
4638
4639 // batch set position (and display animation) to all thumbnails
4640 // first display newly built thumbnails
4641
4642 G.GOM.thumbnails2Display=[];
4643
4644 var duration = ThumbnailPreparePosition( tnToDisplay );
4645 ThumbnailPreparePosition( tnToReDisplay );
4646
4647 ThumbnailDisplayAnimBatch();
4648
4649 if( G.tn.opt.Get('displayTransition') == 'NONE' ) {
4650 G.galleryResizeEventEnabled = true;
4651 // GalleryThumbnailSliderBuildAndStart(); // image slider on last displayed thumbnail
4652 TriggerCustomEvent('galleryDisplayed');
4653 }
4654 else {
4655 // setTimeout(function() {
4656 requestTimeout( function() {
4657 // change value after the end of the display transistion of the newly built thumbnails
4658 G.galleryResizeEventEnabled = true;
4659 // GalleryThumbnailSliderBuildAndStart(); // image slider on last displayed thumbnail
4660 TriggerCustomEvent('galleryDisplayed');
4661 // }, nbBuild * G.tn.opt.Get('displayInterval'));
4662 }, duration * G.tn.opt.Get('displayInterval'));
4663 }
4664
4665 }
4666
4667
4668 function ThumbnailPreparePosition( lstThumb ) {
4669
4670 var nbBuild = lstThumb.length;
4671 if( nbBuild == 0 ) { return 0; }
4672
4673
4674 var displayOrder = G.tn.opt.Get('displayOrder');
4675
4676 if( displayOrder == 'random' ) {
4677 NGY2Tools.AreaShuffle( lstThumb );
4678 }
4679 else {
4680 if( displayOrder == 'rowByRow' && !( G.layout.engine == 'JUSTIFIED' || G.layout.engine == 'GRID' )) {
4681 displayOrder = '';
4682 }
4683 if( (displayOrder == 'colFromRight' || displayOrder == 'colFromLeft' ) && !(G.layout.engine == 'CASCADING' || G.layout.engine == 'GRID' )) {
4684 displayOrder = '';
4685 }
4686 }
4687
4688
4689 // DISPLAY COLUMN BY COLUMN
4690 if( displayOrder == 'colFromRight' || displayOrder == 'colFromLeft' ) {
4691 var tab = [];
4692 var cols = [];
4693 for( var i = 0; i < nbBuild ; i++ ) {
4694 if( tab[lstThumb[i].left] == undefined ) {
4695 tab[lstThumb[i].left] = [];
4696 cols.push( lstThumb[i].left );
4697 }
4698 tab[lstThumb[i].left].push( lstThumb[i].idx )
4699 }
4700 if( displayOrder == 'colFromRight' ) {
4701 cols = cols.reverse();
4702 }
4703 for( var i = 0; i < cols.length; i++ ) {
4704 var col = cols[i];
4705 for( var j = 0; j < tab[col].length; j++ ) {
4706 ThumbnailSetPosition( tab[col][j], i);
4707 }
4708 }
4709 return(i);
4710 }
4711
4712
4713 // STANDARD DISPLAY OR ROW BY ROW
4714 var d = 0;
4715 var top = lstThumb[0].top;
4716 for( var i = 0; i < nbBuild ; i++ ) {
4717 // ThumbnailSetPosition(tnToDisplay[i].idx, tnToDisplay[i].delay+10);
4718 // ThumbnailSetPosition(tnToDisplay[i].idx, i);
4719
4720 if( displayOrder == 'rowByRow' ) {
4721 // DISPLAY ROW BY ROW
4722 if( lstThumb[i].top > top ) {
4723 d++;
4724 top = lstThumb[i].top;
4725 }
4726 }
4727 else {
4728 d++;
4729 }
4730 ThumbnailSetPosition(lstThumb[i].idx, d);
4731 }
4732 return(d);
4733
4734 }
4735
4736 // Thumbnail: set the new position
4737 function ThumbnailSetPosition( GOMidx, cnt ) {
4738 var newTop= 0;
4739 var curTn= G.GOM.items[GOMidx];
4740 var idx= G.GOM.items[GOMidx].thumbnailIdx;
4741 var item= G.I[idx];
4742
4743 if( curTn.neverDisplayed ) {
4744 // thumbnail is built but has never been displayed (=first display)
4745 var top = curTn.top - G.GOM.clipArea.top;
4746 if( G.tn.opt.Get('stacks') > 0 ) {
4747 // we have stacks -> do not display them here. They will be displayed at the end of the display animation
4748 item.$elt.last().css({ display: 'block'});
4749 item.$elt.css({ top: top , left: curTn.left });
4750 }
4751 else {
4752 item.$elt.css({ display: 'block', top: top , left: curTn.left });
4753 }
4754 newTop=top;
4755
4756 // display the image of the thumbnail when fully loaded
4757 if( G.O.thumbnailWaitImageLoaded === true ) {
4758 var gi_imgLoad = ngimagesLoaded( item.$getElt('.nGY2TnImg2') );
4759 gi_imgLoad.on( 'progress', function( instance, image ) {
4760 if( image.isLoaded ) {
4761 var albumIdx = image.img.getAttribute('data-albumidx');
4762 if( albumIdx == G.GOM.albumIdx ) {
4763 // ignore event if not on current album
4764 var idx = image.img.getAttribute('data-idx');
4765 G.I[idx].ThumbnailImageReveal();
4766 }
4767 }
4768 });
4769 }
4770 // display the thumbnail
4771 ThumbnailAppear(GOMidx, cnt);
4772
4773 curTn.displayed = true;
4774 curTn.neverDisplayed = false;
4775 }
4776 else {
4777 var topOld = G.GOM.cache.containerOffset.top + item.top;
4778 var top = G.GOM.cache.containerOffset.top + (curTn.top - G.GOM.clipArea.top);
4779 newTop = curTn.top - G.GOM.clipArea.top;
4780 var vp = G.GOM.cache.viewport;
4781 if( G.O.thumbnailDisplayOutsideScreen || ( ( (topOld + curTn.height) >= (vp.t - vp.h) && topOld <= (vp.t + vp.h * 4) ) ||
4782 ( (top + curTn.height) >= (vp.t - vp.h) && top <= (vp.t + vp.h * 4) ) ) ) {
4783 // thumbnail positioned in enlarged viewport (viewport + 4 x viewport height) (v1.5: changed from 2 to 4)
4784 if( curTn.displayed ) {
4785 // thumbnail is displayed
4786 if( item.top != curTn.top || item.left != curTn.left ) {
4787 // set position
4788 if( G.O.galleryResizeAnimation == true ) {
4789 // with transition
4790 var tweenable = new NGTweenable();
4791 tweenable.tween({
4792 from: { top: item.top, left: item.left, height: item.height, width: item.width },
4793 to: { top: newTop, left: curTn.left, height: curTn.height, width: curTn.width },
4794 attachment: { $e: item.$elt },
4795 duration: 100,
4796 delay: cnt * G.tn.opt.Get('displayInterval') / 5,
4797 // easing: 'easeInOutQuad',
4798 easing: 'easeOutQuart',
4799 step: function (state, att) {
4800 // window.ng_draf( function() {
4801 att.$e.css(state);
4802 // });
4803 },
4804 finish: function (state, att) {
4805 var _this=this;
4806 // window.ng_draf( function() {
4807 _this.dispose();
4808 // });
4809 }
4810 });
4811 }
4812 else {
4813 // set position without transition
4814 // item.$elt.css({ top: curTn.top , left: curTn.left });
4815 item.$elt.css({ top: newTop , left: curTn.left });
4816 }
4817 }
4818 }
4819 else {
4820 // re-display thumbnail
4821 curTn.displayed = true;
4822 // item.$elt.css({ display: 'block', top: curTn.top , left: curTn.left, opacity:1 });
4823 item.$elt.css({ display: 'block', top: newTop, left: curTn.left, opacity: 1 });
4824 ThumbnailAppearFinish(item);
4825 }
4826 }
4827 else {
4828 // undisplay thumbnail if not in viewport+margin --> performance gain
4829 curTn.displayed = false;
4830 item.$elt.css({ display: 'none'});
4831 }
4832 }
4833 item.left = curTn.left;
4834 item.top = newTop;
4835
4836 // set new size if changed
4837 if( item.width != curTn.width || item.height != curTn.height ) {
4838 item.$elt.css({ width: curTn.width , height: curTn.height });
4839 item.width = curTn.width;
4840 item.height = curTn.height;
4841
4842 // if( curTn.resizedContentWidth > 0 ) {
4843 // resize also the content (=image)
4844 if( item.resizedContentWidth != curTn.resizedContentWidth || item.resizedContentHeight != curTn.resizedContentHeight ) {
4845 if( item.kind == 'albumUp' ) {
4846 // item.$getElt('.nGY2GThumbnailAlbumUp').css({'height': curTn.resizedContentHeight, 'width': curTn.resizedContentWidth});
4847 }
4848 else {
4849 item.$getElt('.nGY2GThumbnailImage').css({'height': curTn.resizedContentHeight, 'width': curTn.resizedContentWidth});
4850
4851 if( G.layout.engine == 'JUSTIFIED' ) {
4852 item.$getElt('.nGY2GThumbnailImg').css({'height': curTn.resizedContentHeight, 'width': curTn.resizedContentWidth});
4853 }
4854 }
4855 item.resizedContentWidth = curTn.resizedContentWidth;
4856 item.resizedContentHeight = curTn.resizedContentHeight;
4857 }
4858 }
4859
4860
4861 // add counter of remaining (not displayed) images
4862 if( G.GOM.lastDisplayedIdxNew == GOMidx && G.layout.support.rows ) {
4863 if( (G.galleryDisplayMode.Get() == 'ROWS' && G.galleryMaxRows.Get() > 0) || (G.galleryDisplayMode.Get() == 'FULLCONTENT' && G.galleryLastRowFull.Get() && G.GOM.lastFullRow != -1) ){
4864 // number of items
4865 var nb = G.GOM.items.length - GOMidx - 1;
4866 if( item.albumID != '0' && G.O.thumbnailLevelUp ) {
4867 nb--;
4868 }
4869
4870 if( nb > 0 ) {
4871 // display counter
4872 if( G.O.thumbnailOpenInLightox || G.O.thumbnailSliderDelay > 0 ) {
4873 item.$getElt('.nGY2GThumbnailIconsFullThumbnail').html( '+' + nb);
4874 }
4875
4876 // if( G.layout.engine == 'GRID' && G.GOM.slider.hostItem != G.GOM.NGY2Item(GOMidx) ) {
4877 // image slider on last displayed thumbnail
4878 if( G.O.thumbnailLabel.get('position') != 'right' && G.O.thumbnailLabel.get('position') != 'left' ) {
4879 if( G.GOM.slider.hostItem != G.GOM.NGY2Item(GOMidx) ) {
4880
4881 // set current slider back to initial content
4882 GalleryThumbnailSliderSetContent( G.GOM.slider.hostItem );
4883 // new slider
4884 G.GOM.slider.hostIdx = GOMidx;
4885 G.GOM.slider.hostItem = G.GOM.NGY2Item(GOMidx);
4886 G.GOM.slider.nextIdx = GOMidx;
4887 G.GOM.slider.currentIdx = GOMidx;
4888 GalleryThumbnailSliderBuildAndStart(); // image slider on last displayed thumbnail
4889 // GalleryThumbnailSliderSetNextContent();
4890 }
4891 }
4892 }
4893 else {
4894 // reset slider content to initial content because all thumbnails are displayed
4895 GalleryThumbnailSliderSetContent( G.GOM.slider.hostItem );
4896 G.GOM.slider.hostIdx = -1;
4897 }
4898
4899 G.GOM.lastDisplayedIdx = GOMidx;
4900 }
4901 }
4902
4903 }
4904
4905 // ---------------------
4906 // replace image on last thumbnails with not displayed ones (mode ROWS or FULLCONTENT with galleryLastRowFull enabled)
4907 // function GalleryLastThumbnailSlideImage() {
4908 function GalleryThumbnailSliderBuildAndStart() {
4909
4910 if( G.O.thumbnailSliderDelay == 0 || G.GOM.slider.hostIdx == -1 ) {
4911 return;
4912 }
4913 clearTimeout(G.GOM.slider.timerID);
4914
4915 var item = G.GOM.slider.hostItem;
4916
4917 // dupplicate image layer -> for the next image
4918 if( item.$getElt('.nGY2TnImgNext').length == 0 ) {
4919 item.$getElt('.nGY2TnImg').clone().removeClass('nGY2TnImg').addClass('nGY2TnImgNext').insertAfter(item.$getElt('.nGY2TnImg'));
4920 item.$getElt('.nGY2TnImgBack').clone().removeClass('nGY2TnImgBack').addClass('nGY2TnImgBackNext').insertAfter(item.$getElt('.nGY2TnImg', true));
4921 item.$getElt('.nGY2GThumbnailImage', true); // important -> refresh the cache
4922 item.$getElt('.nGY2GThumbnailImg', true); // important -> refresh the cache
4923 }
4924
4925 item.CSSTransformSet('.nGY2TnImgNext', 'translateX', '100%', true);
4926 item.CSSTransformApply( '.nGY2TnImgNext' );
4927 item.CSSTransformSet('.nGY2TnImgBackNext', 'translateX', '100%', true);
4928 item.CSSTransformApply( '.nGY2TnImgBackNext' );
4929
4930 GalleryThumbnailSliderSetNextContent();
4931
4932 // clearTimeout(G.GOM.slider.timerID);
4933 // G.GOM.slider.timerID = setTimeout(function(){ GalleryThumbnailSliderStartTransition() }, G.O.thumbnailSliderDelay);
4934 G.GOM.slider.timerID = requestTimeout(function(){ GalleryThumbnailSliderStartTransition() }, G.O.thumbnailSliderDelay);
4935 }
4936
4937
4938 function GalleryThumbnailSliderSetNextContent() {
4939
4940 G.GOM.slider.nextIdx++;
4941 if( G.GOM.slider.nextIdx >= G.GOM.items.length ) {
4942 G.GOM.slider.nextIdx = G.GOM.slider.hostIdx;
4943 }
4944
4945 // new image
4946 var newItem = G.GOM.NGY2Item(G.GOM.slider.nextIdx);
4947 // var imgBlurred = G.emptyGif;
4948 var bgImg = "url('" + G.emptyGif + "')";
4949 if( newItem.imageDominantColors != null ) {
4950 // imgBlurred = newItem.imageDominantColors;
4951 bgImg = "url('" + newItem.imageDominantColors + "')";
4952 }
4953 G.GOM.slider.hostItem.$getElt('.nGY2TnImgBackNext', true).css({'background-image': bgImg, opacity: 1 });
4954 G.GOM.slider.hostItem.$getElt('.nGY2TnImgNext', true).css({ 'background-image': "url('" + newItem.thumbImg().src + "')", opacity: 1 });
4955 G.GOM.slider.hostItem.$getElt('.nGY2TnImgNext .nGY2GThumbnailImg', true).attr('src', newItem.thumbImg().src );
4956
4957
4958 }
4959
4960 // thumbnail slider - transition from one image to the next one
4961 function GalleryThumbnailSliderStartTransition() {
4962
4963 if( G.GOM.slider.hostItem.$getElt() != null ) {
4964
4965 // slider transition
4966 var tweenable = new NGTweenable();
4967 G.GOM.slider.tween = tweenable;
4968 tweenable.tween({
4969 from: { 'left': 100 },
4970 to: { 'left': 0 },
4971 duration: 800,
4972 delay: 0,
4973 // easing: 'easeInOutQuad',
4974 easing: 'easeOutQuart',
4975
4976 step: function (state) {
4977 if( G.GOM.slider.hostItem.$getElt() == null ) {
4978 // the thumbnail may have been destroyed since the start of the animation
4979 G.GOM.slider.tween.stop(false);
4980 return;
4981 }
4982
4983 // window.ng_draf( function() {
4984 // slide current content
4985 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgBack', 'translateX', -(100 - state.left) + '%');
4986 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgBack' );
4987 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImg', 'translateX', -(100 - state.left) + '%');
4988 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImg' );
4989
4990 // slide new content
4991 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgBackNext', 'translateX', state.left + '%');
4992 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgBackNext' );
4993 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgNext', 'translateX', state.left + '%');
4994 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgNext' );
4995 // });
4996
4997
4998 },
4999 finish: function (state) {
5000 if( G.GOM.slider.hostItem.$getElt() == null ) {
5001 // the thumbnail may be destroyed since the start of the animation
5002 return;
5003 }
5004
5005 if( G.GOM.NGY2Item(G.GOM.slider.nextIdx) == null ) { return; } // item does not exist anymore
5006
5007 // window.ng_draf( function() {
5008 // set new content as current content
5009 GalleryThumbnailSliderSetContent( G.GOM.NGY2Item(G.GOM.slider.nextIdx) );
5010 G.GOM.slider.currentIdx = G.GOM.slider.nextIdx;
5011 GalleryThumbnailSliderSetNextContent();
5012
5013 clearTimeout(G.GOM.slider.timerID);
5014 // G.GOM.slider.timerID=setTimeout(function(){ GalleryThumbnailSliderStartTransition() }, G.O.thumbnailSliderDelay);
5015 G.GOM.slider.timerID = requestTimeout(function(){ GalleryThumbnailSliderStartTransition() }, G.O.thumbnailSliderDelay);
5016 // });
5017 }
5018 });
5019 }
5020 }
5021
5022 // set main content of the thumbnail hosting the slider
5023 // hide the elements for the next content of the slider
5024 function GalleryThumbnailSliderSetContent( ngy2itemContent ) {
5025 if( G.GOM.slider.hostIdx == -1 ) { return; }
5026
5027 if( G.GOM.slider.tween != null ) {
5028 if( G.GOM.slider.tween._isTweening == true ) {
5029 G.GOM.slider.tween.stop(false);
5030 }
5031 }
5032
5033 var bgImg = "url('" + G.emptyGif + "')";
5034 if( ngy2itemContent.imageDominantColors != null ) {
5035 bgImg = "url('" + ngy2itemContent.imageDominantColors + "')";
5036 }
5037 G.GOM.slider.hostItem.$getElt('.nGY2TnImgBack').css('background-image', bgImg);
5038 G.GOM.slider.hostItem.$getElt('.nGY2TnImg').css('background-image', "url('" + ngy2itemContent.thumbImg().src + "')" );
5039 G.GOM.slider.hostItem.$getElt('.nGY2TnImg .nGY2GThumbnailImg').attr('src', ngy2itemContent.thumbImg().src );
5040
5041 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgBack', 'translateX', '0');
5042 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgBack' );
5043 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImg', 'translateX', '0');
5044 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImg' );
5045
5046 // place the containers for the next image slider outside of the thumbnail (=hidden)
5047 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgBackNext', 'translateX', '100%', true);
5048 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgBackNext' );
5049 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgNext', 'translateX', '100%', true);
5050 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgNext' );
5051
5052 // set new title and description
5053 if( G.O.thumbnailLabel.get('display') == true ) {
5054 var icons = G.O.icons.thumbnailAlbum;
5055 if( ngy2itemContent.kind != 'album' ) {
5056 icons = G.O.icons.thumbnailImage;
5057 }
5058 G.GOM.slider.hostItem.$getElt('.nGY2GThumbnailTitle').html(icons + getThumbnailTitle(ngy2itemContent));
5059 G.GOM.slider.hostItem.$getElt('.nGY2GThumbnailDescription').html(icons + getTumbnailDescription(ngy2itemContent));
5060 }
5061 }
5062
5063
5064
5065 // Compute the height of the label part of a thumbnail (title+description, both single line)
5066 function ThumbnailGetLabelHeight() {
5067 var newElt = [],
5068 newEltIdx = 0;
5069
5070 // if( G.O.thumbnailLabel.get('display') == false && G.tn.toolbar.getWidth(item) <= 0 ) {
5071 if( G.O.thumbnailLabel.get('display') == false ) {
5072 return 0;
5073 }
5074
5075 // var desc='';
5076 // if( G.O.thumbnailLabel.get('displayDescription') == true ) {
5077 // desc = 'aAzZjJ';
5078 // }
5079
5080 // visibility set to hidden
5081 newElt[newEltIdx++] = '<div class="nGY2GThumbnail ' + G.O.theme + '" style="display:block;visibility:hidden;position:absolute;top:-9999px;left:-9999px;" ><div class="nGY2GThumbnailSub">';
5082 if( G.O.thumbnailLabel.get('display') == true ) {
5083 // Labels: title and description
5084 newElt[newEltIdx++] = ' <div class="nGY2GThumbnailLabel" '+ G.tn.style.getLabel() +'>';
5085 newElt[newEltIdx++] = ' <div class="nGY2GThumbnailAlbumTitle" '+G.tn.style.getTitle()+'>aAzZjJ</div>';
5086 if( G.O.thumbnailLabel.get('displayDescription') == true ) {
5087 newElt[newEltIdx++] = ' <div class="nGY2GThumbnailDescription" '+G.tn.style.getDesc()+'>'+'aAzZjJ'+'</div>';
5088 }
5089 newElt[newEltIdx++] = ' </div>';
5090 }
5091
5092 newElt[newEltIdx++] = '</div></div>';
5093
5094 var $newDiv = jQuery(newElt.join('')).appendTo(G.$E.conTn);
5095 var h = $newDiv.find('.nGY2GThumbnailLabel').outerHeight(true);
5096 $newDiv.remove();
5097
5098 return h;
5099 }
5100
5101 function ThumbnailBuildStacks( bgColor ) {
5102 var ns=G.tn.opt.Get('stacks');
5103 if( ns == 0 ) { return ''; }
5104
5105 var s='';
5106 for( var i=0; i<ns; i++ ) {
5107 s='<div class="nGY2GThumbnailStack " style="display:none;'+bgColor+'"></div>'+s;
5108 }
5109 return s;
5110 }
5111
5112 //----- Build one UP thumbnail (=navigation thumbnail)
5113 function ThumbnailBuildAlbumpUp( item, GOMidx ) {
5114 // function ThumbnailBuildAlbumpUp( item, idx, GOMidx ) {
5115 var newElt = [],
5116 newEltIdx = 0;
5117
5118 var mp = '';
5119 if( G.O.thumbnailOpenInLightox === false ) {
5120 mp = 'cursor:default;'
5121 }
5122
5123 newElt[newEltIdx++] = ThumbnailBuildStacks('') + '<div class="nGY2GThumbnail" style="display:none;opacity:0;' + mp + '" >';
5124 newElt[newEltIdx++] = ' <div class="nGY2GThumbnailSub">';
5125
5126 var h=G.tn.defaultSize.getHeight(),
5127 w=G.tn.defaultSize.getWidth();
5128
5129 newElt[newEltIdx++] = ' <div class="nGY2GThumbnailImage" style="width:'+w+'px;height:'+h+'px;"><img class="nGY2GThumbnailImg" src="'+G.emptyGif+'" alt="" style="max-width:'+w+'px;max-height:'+h+'px;" ></div>';
5130 // newElt[newEltIdx++] = ' <div class="nGY2GThumbnailAlbumUp" style="width:'+w+'px;height:'+h+'px;">'+G.O.icons.thumbnailAlbumUp+'</div>';
5131 newElt[newEltIdx++] = ' <div class="nGY2GThumbnailAlbumUp" >'+G.O.icons.thumbnailAlbumUp+'</div>';
5132 newElt[newEltIdx++] = ' </div>';
5133 newElt[newEltIdx++] = '</div>';
5134
5135 var $newDiv = jQuery(newElt.join('')).appendTo(G.$E.conTn); //.animate({ opacity: 1},1000, 'swing'); //.show('slow'); //.fadeIn('slow').slideDown('slow');
5136
5137 item.$elt = $newDiv;
5138 $newDiv.data('index', GOMidx);
5139 item.$getElt('.nGY2GThumbnailImg').data('index', GOMidx);
5140
5141 return;
5142 }
5143
5144
5145 //----- Build one thumbnail
5146 function ThumbnailBuild( item, idx, GOMidx ) {
5147 // function ThumbnailBuild( item, idx, GOMidx, lastOne ) {
5148 item.eltTransform = [];
5149 item.eltFilter = [];
5150 item.hoverInitDone = false;
5151 item.$Elts = [];
5152
5153 if( item.kind == 'albumUp' ) {
5154 ThumbnailBuildAlbumpUp( item, GOMidx);
5155 return;
5156 }
5157
5158 var newElt = [],
5159 newEltIdx = 0;
5160
5161 var mp = '';
5162 if( G.O.thumbnailOpenInLightox === false ) {
5163 mp = 'cursor:default;'
5164 }
5165
5166 // var src = encodeURI(item.thumbImg().src),
5167 var src = (item.thumbImg().src).replace(/'/g, "%27"), // replace single quote with %27
5168 sTitle = getThumbnailTitle(item);
5169
5170 // image background -> visible during image download
5171 var bg = '';
5172 var bgImg = "background-image: url('" + G.emptyGif + "');";
5173 if( item.imageDominantColors != null ) {
5174 // dominant colorS (blurred preview image)
5175 bgImg = "background-image: url('" + item.imageDominantColors + "');";
5176 }
5177 else {
5178 // dominant color -> background color
5179 if( item.imageDominantColor != null ) {
5180 bg = 'background-color:' + item.imageDominantColor + ';';
5181 }
5182 else {
5183 bgImg = '';
5184 }
5185 }
5186
5187 var op = 'opacity:1;';
5188 if( G.O.thumbnailWaitImageLoaded == true ) {
5189 op = 'opacity:0;';
5190 }
5191
5192 // ##### thumbnail containers (with stacks)
5193 newElt[newEltIdx++] = ThumbnailBuildStacks(bg) + '<div class="nGY2GThumbnail nGY2GThumbnail_'+G.GOM.curNavLevel+'" style="display:none;opacity:0;' + mp + '"><div class="nGY2GThumbnailSub ' + ( G.O.thumbnailSelectable && item.selected ? "nGY2GThumbnailSubSelected" : "" ) + '">';
5194
5195
5196 // image size
5197 var w = G.tn.settings.getW();
5198 var h = G.tn.settings.getH();
5199 if( G.tn.settings.getMosaic() !== null ) {
5200 // mosaic layout ->
5201 w = G.GOM.items[GOMidx].width;
5202 h = G.GOM.items[GOMidx].height;
5203 }
5204
5205 var bgSize = 'contain';
5206 if( G.tn.opt.Get('crop') ) {
5207 bgSize = 'cover'; // thumbnail image will be cropped to fit in the thumbnail (no black border)
5208 }
5209
5210 // ##### layer for image background (color, dominant color, blurred preview)
5211 var s1 = "position: absolute; top: 0px; left: 0px; width:" + w + "px; height:" + h + "px;"+ bg + bgImg + " background-position: center center; background-repeat: no-repeat; background-size:" + bgSize + "; overflow: hidden;";
5212 newElt[newEltIdx++]='<div class="nGY2GThumbnailImage nGY2TnImgBack" style="' + s1 + '"></div>';
5213
5214 // #### layer for image
5215 var s2 = op + "position: absolute; top: 0px; left: 0px; width:" + w + "px; height:" + h + "px; background-image: url('" + src + "'); background-position: center center; background-repeat: no-repeat; background-size:" + bgSize + "; overflow: hidden;";
5216 newElt[newEltIdx++]='<div class="nGY2GThumbnailImage nGY2TnImg" style="' + s2 + '">';
5217 newElt[newEltIdx++]=' <img class="nGY2GThumbnailImg nGY2TnImg2" src="' + src + '" alt="' + sTitle + '" style="opacity:0;" data-idx="' + idx + '" data-albumidx="' + G.GOM.albumIdx + '" >';
5218 newElt[newEltIdx++]='</div>';
5219
5220 // ##### layer for user customization purposes
5221 newElt[newEltIdx++]='<div class="nGY2GThumbnailCustomLayer"></div>';
5222
5223 // ##### layer for labels (title + description and their icons)
5224 if( G.O.thumbnailLabel.get('display') == true ) {
5225 // Labels: title and description
5226 newElt[newEltIdx++]= ' <div class="nGY2GThumbnailLabel" '+ G.tn.style.getLabel(item) + '>';
5227 if( item.kind == 'album' ) {
5228 // album kind
5229 newElt[newEltIdx++]= ' <div class="nGY2GThumbnailTitle nGY2GThumbnailAlbumTitle" ' + G.tn.style.getTitle() + '>' + G.O.icons.thumbnailAlbum + sTitle + '</div>';
5230 }
5231 else {
5232 // image/media kind
5233 newElt[newEltIdx++]= ' <div class="nGY2GThumbnailTitle nGY2GThumbnailImageTitle" ' + G.tn.style.getTitle() + '>' + G.O.icons.thumbnailImage + sTitle + '</div>';
5234 }
5235 newElt[newEltIdx++]= ' <div class="nGY2GThumbnailDescription" ' + G.tn.style.getDesc() + '>' + getTumbnailDescription(item) + '</div>';
5236 newElt[newEltIdx++]= ' </div>';
5237 }
5238
5239 // ##### layer for tools
5240 // newElt[newEltIdx++] = ThumbnailBuildTools(item, lastOne);
5241 newElt[newEltIdx++] = ThumbnailBuildTools(item);
5242
5243 // close containers
5244 newElt[newEltIdx++]='</div></div>';
5245
5246 var $newDiv =jQuery(newElt.join('')).appendTo(G.$E.conTn);
5247
5248 item.$elt=$newDiv;
5249 $newDiv.data('index',GOMidx);
5250 item.$getElt('.nGY2GThumbnailImg').data('index',GOMidx);
5251
5252 // Custom init function
5253 var fu=G.O.fnThumbnailInit;
5254 if( fu !== null ) {
5255 typeof fu == 'function' ? fu($newDiv, item, GOMidx) : window[fu]($newDiv, item, GOMidx);
5256 }
5257
5258 if( item.title != 'image gallery by nanogallery2 [build]' ) {
5259 ThumbnailOverInit(GOMidx);
5260 }
5261
5262 return ;
5263 }
5264
5265
5266 // Thumbnail layer for tools (toolbars and counter)
5267 function ThumbnailBuildTools( item ) {
5268
5269 // toolbars
5270 var tb = ThumbnailBuildToolbarOne(item, 'topLeft') + ThumbnailBuildToolbarOne(item, 'topRight') + ThumbnailBuildToolbarOne(item, 'bottomLeft') + ThumbnailBuildToolbarOne(item, 'bottomRight');
5271
5272 // counter of not displayed images
5273 tb += '<div class="nGY2GThumbnailIconsFullThumbnail"></div>';
5274
5275 return tb;
5276 }
5277
5278 function ThumbnailBuildToolbarOne( item, position ) {
5279 var toolbar = '';
5280 var tb = G.tn.toolbar.get(item);
5281 var width = { xs:0, sm:1, me:2, la:3, xl:4 };
5282 var cnt = 0;
5283
5284 if( tb[position] != '' ) {
5285 var pos='top: 0; right: 0; text-align: right;'; // 'topRight' and default
5286 switch( position ) {
5287 case 'topLeft':
5288 pos = 'top: 0; left: 0; text-align: left;';
5289 break;
5290 case 'bottomRight':
5291 pos = 'bottom: 0; right: 0; text-align: right;';
5292 break;
5293 case 'bottomLeft':
5294 pos = 'bottom: 0; left: 0; text-align: left;';
5295 break;
5296 }
5297
5298 toolbar += ' <ul class="nGY2GThumbnailIcons" style="' + pos + '">';
5299
5300 var icons = tb[position].split(',');
5301 var nb = icons.length;
5302 for( var i = 0; i < nb; i++ ) {
5303 var icon = icons[i].replace(/^\s*|\s*$/, ''); //trim trailing/leading whitespace
5304
5305 var minWidth = icon.substring(0,2).toLowerCase();
5306 var tIcon = icon;
5307 var display = true;
5308 if( /xs|sm|me|la|xl/i.test(minWidth) ) {
5309 // check visbility (depending on screen width)
5310 if( width[minWidth] > width[G.GOM.curWidth] ) {
5311 display = false;
5312 }
5313 tIcon = icon.substring(2);
5314 }
5315
5316 if( display ) {
5317 var sp=(i+1<nb ? '&nbsp;' :'');
5318 switch( tIcon ) {
5319 case 'COUNTER':
5320 if( item.kind == 'album' ) {
5321 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="">';
5322 toolbar += ' <div class="nGY2GThumbnailIconImageCounter"></div>';
5323 toolbar += ' <div class="nGY2GThumbnailIconText">' + G.O.icons.thumbnailCounter+Math.max((item.getContentLength(false)),item.numberItems) + sp + '</div>';
5324 toolbar += ' </li>';
5325 cnt++;
5326 }
5327 break;
5328 case 'COUNTER2':
5329 if( item.kind == 'album' ) {
5330 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="">';
5331 toolbar += ' <div class="nGY2GThumbnailIconTextBadge">' + G.O.icons.thumbnailCounter+Math.max((item.getContentLength(false)),item.numberItems) + sp + '</div>';
5332 toolbar += ' </li>';
5333 cnt++;
5334 }
5335 break;
5336 case 'SHARE':
5337 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="' + tIcon + '">';
5338 toolbar += ' <div>' + G.O.icons.thumbnailShare + '</div>';
5339 toolbar += ' </li>';
5340 cnt++;
5341 break;
5342 case 'DOWNLOAD':
5343 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="' + tIcon + '">';
5344 toolbar += ' <div>' + G.O.icons.thumbnailDownload + '</div>';
5345 toolbar += ' </li>';
5346 cnt++;
5347 break;
5348 case 'INFO':
5349 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="' + tIcon + '">';
5350 toolbar += ' <div>' + G.O.icons.thumbnailInfo + '</div>';
5351 toolbar += ' </li>';
5352 cnt++;
5353 break;
5354 case 'SHOPPINGCART':
5355 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="' + tIcon + '">';
5356 // toolbar += ' <div>' + G.O.icons.thumbnailShoppingcart + '</div>';
5357 toolbar += ThumbnailBuildToolbarOneCart( item );
5358
5359 toolbar += ' </li>';
5360 cnt++;
5361 break;
5362 case 'DISPLAY':
5363 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="DISPLAY">';
5364 toolbar += ' <div class="nGY2GThumbnailIconImageShare">' + G.O.icons.thumbnailDisplay + '</div>';
5365 toolbar += ' </li>';
5366 cnt++;
5367 break;
5368 case 'CUSTOM1':
5369 case 'CUSTOM2':
5370 case 'CUSTOM3':
5371 case 'CUSTOM4':
5372 case 'CUSTOM5':
5373 case 'CUSTOM6':
5374 case 'CUSTOM7':
5375 case 'CUSTOM8':
5376 case 'CUSTOM9':
5377 case 'CUSTOM10':
5378 var cust = tIcon.replace('CUSTOM', '');
5379 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="' + tIcon.toLowerCase() + '">';
5380 toolbar += ' <div class="nGY2GThumbnailIconImageShare">' + G.O.icons['thumbnailCustomTool' + cust] + '</div>';
5381 toolbar += ' </li>';
5382 cnt++;
5383 break;
5384 case 'FEATURED':
5385 if( item.featured === true ) {
5386 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="">';
5387 toolbar += ' <div class="nGY2GThumbnailIconImageFeatured">' + G.O.icons.thumbnailFeatured + '</div>';
5388 toolbar += ' </li>';
5389 cnt++;
5390 }
5391 break;
5392 case 'SELECT':
5393 if( G.O.thumbnailSelectable == true ) {
5394 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="TOGGLESELECT">';
5395 if( item.selected === true ) {
5396 toolbar += ' <div class="nGY2GThumbnailIconImageSelect nGY2ThumbnailSelected">' + G.O.icons.thumbnailSelected + '</div>';
5397 }
5398 else {
5399 toolbar += ' <div class="nGY2GThumbnailIconImageSelect nGY2ThumbnailUnselected">' + G.O.icons.thumbnailUnselected + '</div>';
5400 }
5401 toolbar += ' </li>';
5402 cnt++;
5403 }
5404 break;
5405 }
5406 }
5407 }
5408 toolbar += ' </ul>';
5409 }
5410
5411 if( cnt > 0 ) {
5412 return toolbar;
5413 }
5414 else {
5415 return '';
5416 }
5417 }
5418
5419 // CART ICON AND COUNTER
5420 function ThumbnailBuildToolbarOneCart( item ) {
5421 var q = 0;
5422
5423 var id = item.GetID()
5424 for( var i=0; i<G.shoppingCart.length; i++ ) {
5425 if( G.I[G.shoppingCart[i].idx].GetID() == id ) {
5426 q = G.shoppingCart[i].qty;
5427 }
5428 }
5429 if( q == 0 ) {
5430 q = '';
5431 }
5432
5433 return ' <div>' + G.O.icons.thumbnailShoppingcart + q + '</div>';
5434 }
5435 function ThumbnailBuildToolbarOneCartUpdate( item ) {
5436 var $e = item.$elt;
5437
5438 if( $e != null ) {
5439 var $q = $e.find('*[data-ngy2action="SHOPPINGCART"]');
5440 if( $q !== undefined ) {
5441 $q.html( ThumbnailBuildToolbarOneCart( item ) );
5442 }
5443 }
5444 }
5445
5446 function getThumbnailTitle( item ) {
5447
5448 var sTitle = item.title;
5449 if( G.O.thumbnailLabel.get('display') == true ) {
5450 if( sTitle === undefined || sTitle.length == 0 ) { sTitle = '&nbsp;'; }
5451
5452 if( G.i18nTranslations.thumbnailImageTitle != '' ) {
5453 sTitle = G.i18nTranslations.thumbnailImageTitle;
5454 }
5455 var ml = G.O.thumbnailLabel.get('titleMaxLength');
5456 if( ml > 3 && sTitle.length > ml ){
5457 sTitle = sTitle.substring(0, ml) + '...';
5458 }
5459 }
5460
5461 return sTitle;
5462 }
5463
5464 function getTumbnailDescription( item ) {
5465 var sDesc = '';
5466 if( G.O.thumbnailLabel.get('displayDescription') == true ) {
5467 if( item.kind == 'album' ) {
5468 if( G.i18nTranslations.thumbnailImageDescription != '' ) {
5469 sDesc = G.i18nTranslations.thumbnailAlbumDescription;
5470 }
5471 else {
5472 sDesc = item.description;
5473 }
5474 }
5475 else {
5476 if( G.i18nTranslations.thumbnailImageDescription != '' ) {
5477 sDesc = G.i18nTranslations.thumbnailImageDescription;
5478 }
5479 else {
5480 sDesc = item.description;
5481 }
5482 }
5483 var ml = G.O.thumbnailLabel.get('descriptionMaxLength');
5484 if( ml > 3 && sDesc.length > ml ){
5485 sDesc = sDesc.substring(0, ml) + '...';
5486 }
5487 if( sDesc.length == 0 ) {
5488 sDesc = '&nbsp;';
5489 }
5490 }
5491
5492 return sDesc;
5493 }
5494
5495
5496
5497 // Retrieve the maximum number of thumbnails that fits in one row
5498 function NbThumbnailsPerRow( areaWidth ) {
5499 var tnW = G.tn.defaultSize.getOuterWidth();
5500
5501 var nbMaxTn = 0;
5502 if( G.O.thumbnailAlignment == 'justified' ) {
5503 nbMaxTn = Math.floor((areaWidth)/(tnW));
5504 }
5505 else {
5506 nbMaxTn = Math.floor((areaWidth + G.tn.settings.GetResponsive('gutterWidth'))/(tnW + G.tn.settings.GetResponsive('gutterWidth')));
5507 }
5508
5509 if( G.O.maxItemsPerLine >0 && nbMaxTn > G.O.maxItemsPerLine ) {
5510 nbMaxTn = G.O.maxItemsPerLine;
5511 }
5512
5513 if( nbMaxTn < 1 ) { nbMaxTn = 1; }
5514
5515 return nbMaxTn
5516 }
5517
5518 // Thumbnail display animation
5519 function ThumbnailAppear( n, cnt ) {
5520 var curTn = G.GOM.items[n];
5521 var item = G.I[curTn.thumbnailIdx];
5522
5523
5524 if( G.tn.opt.Get('displayTransition') == 'NONE' ) {
5525 item.$elt.css({ opacity: 1 });
5526 ThumbnailAppearFinish( item );
5527 }
5528 else {
5529 if( item.$elt == null ) { return; }
5530 var top = G.GOM.cache.containerOffset.top + ( curTn.top - G.GOM.clipArea.top );
5531 var vp = G.GOM.cache.viewport;
5532 if( (top + (curTn.top - G.GOM.clipArea.top)) >= (vp.t - 50) && top <= (vp.t + vp.h + 50) ) {
5533 // display animation only if in the current viewport
5534 var delay = cnt * G.tn.opt.Get('displayInterval');
5535 if( G.tn.opt.Get('displayTransition') == 'CUSTOM' ) {
5536 if( G.GOM.curNavLevel == 'lN' ) {
5537 G.O.fnThumbnailDisplayEffect(item.$elt, item, n, delay);
5538 }
5539 else {
5540 G.O.fnThumbnailL1DisplayEffect(item.$elt, item, n, delay);
5541 }
5542 }
5543 else {
5544 G.GOM.thumbnails2Display.push({itm: item, d: delay});
5545 // ThumbnailDisplayAnim2(item, delay);
5546 }
5547 return;
5548 }
5549 else {
5550 item.$elt.css({ opacity: 1 });
5551 ThumbnailAppearFinish(item);
5552 }
5553 }
5554 }
5555
5556
5557 // displays thumbnail stacks at the end of the display animation
5558 function ThumbnailAppearFinish( item ) {
5559
5560 // add stacks
5561 var ns = G.tn.opt.Get('stacks');
5562 if( ns > 0 ) {
5563 // display stacks
5564 item.$elt.css({ display: 'block'});
5565 var o = 0.9;
5566 // set stack opacity
5567 for( var i = ns-1; i>=0; i-- ) {
5568 item.$elt.eq(i).css('opacity', o);
5569 o = o - 0.2;
5570 }
5571
5572 }
5573 }
5574
5575
5576 function ThumbnailDisplayAnim2( item, delay ) {
5577 function randomIntFromInterval(min,max) {
5578 return Math.floor(Math.random()*(max-min+1)+min);
5579 }
5580 var oFrom = {};
5581 var oTo = {};
5582
5583 switch (G.tn.opt.Get('displayTransition')) {
5584 case 'RANDOMSCALE': {
5585 var scales = [0.95, 1, 1.05, 1.1];
5586 var zi = [1, 2, 3, 4];
5587
5588 var r = randomIntFromInterval(0,3);
5589 while( r == G.GOM.lastRandomValue ) {
5590 r = randomIntFromInterval(0,3);
5591 }
5592 G.GOM.lastRandomValue = r;
5593 let f = scales[r];
5594 // item.$elt.css({ 'z-index': G.GOM.lastZIndex+zi[r], 'box-shadow': '-1px 2px 5px 1px rgba(0, 0, 0, 0.7)' });
5595 item.$elt.css({ 'z-index': G.GOM.lastZIndex+zi[r], 'box-shadow': '0px 0px 5px 3px rgba(0,0,0,0.74)' });
5596
5597 oFrom = { scale: 0.5, opacity:0 };
5598 oTo = { scale: f, opacity:1 };
5599 break;
5600 }
5601
5602 case 'SCALEUP': {
5603 let f = G.tn.opt.Get('displayTransitionStartVal');
5604 if( f == 0 ) { f = 0.6; } // default value
5605 oFrom = { scale: f, opacity: 0 };
5606 oTo = { scale: 1, opacity: 1 };
5607 break;
5608 }
5609
5610 case 'SCALEDOWN': {
5611 let f = G.tn.opt.Get('displayTransitionStartVal');
5612 if( f == 0 ) { f=1.3; } // default value
5613 oFrom = { scale: f, opacity: 0 };
5614 oTo = { scale: 1, opacity: 1 };
5615 break;
5616 }
5617 case 'SLIDEUP': {
5618 let f = G.tn.opt.Get('displayTransitionStartVal');
5619 if( f == 0 ) { f=50; } // default value
5620 oFrom = { opacity: 0, translateY: f };
5621 oTo = { opacity: 1, translateY: 0 };
5622 break;
5623 }
5624 case 'SLIDEDOWN': {
5625 let f=G.tn.opt.Get('displayTransitionStartVal');
5626 if( f == 0 ) { f=-50; } // default value
5627 oFrom = { opacity: 0, translateY: f };
5628 oTo = { opacity: 1, translateY: 0 };
5629 break;
5630 }
5631 case 'FLIPUP': {
5632 let f=G.tn.opt.Get('displayTransitionStartVal');
5633 if( f == 0 ) { f=100; } // default value
5634 oFrom = { opacity: 0, translateY: f, rotateX: 45 };
5635 oTo = { opacity: 1, translateY: 0, rotateX: 0 };
5636 break;
5637 }
5638 case 'FLIPDOWN': {
5639 let f=G.tn.opt.Get('displayTransitionStartVal');
5640 if( f == 0 ) { f=-100; } // default value
5641 oFrom = { opacity: 0, translateY: f, rotateX: -45 };
5642 oTo = { opacity: 1, translateY: 0, rotateX: 0 };
5643 break;
5644 }
5645 case 'SLIDEUP2': {
5646 let f = G.tn.opt.Get('displayTransitionStartVal');
5647 if( f == 0 ) { f=100; } // default value
5648 oFrom = { opacity: 0, translateY: f, rotateY: 40 };
5649 oTo = { opacity: 1, translateY: 0, rotateY: 0 };
5650 break;
5651 }
5652 case 'IMAGESLIDEUP': {
5653 // let f = G.tn.opt.Get('displayTransitionStartVal');
5654 // if( f == 0 ) { f=100; } // default value
5655 oFrom = { opacity: 0, top: '100%' };
5656 oTo = { opacity: 1, top: '0%' };
5657 break;
5658 }
5659 case 'SLIDEDOWN2': {
5660 let f=G.tn.opt.Get('displayTransitionStartVal');
5661 if( f == 0 ) { f=-100; } // default value
5662 oFrom = { opacity: 0, translateY: f, rotateY: 40 };
5663 oTo = { opacity: 1, translateY: 0, rotateY: 0 };
5664 break;
5665 }
5666 case 'SLIDERIGHT': {
5667 let f=G.tn.opt.Get('displayTransitionStartVal');
5668 if( f == 0 ) { f=-150; } // default value
5669 oFrom = { opacity: 0, translateX: f };
5670 oTo = { opacity: 1, translateX: 0 };
5671 break;
5672 }
5673 case 'SLIDELEFT': {
5674 let f=G.tn.opt.Get('displayTransitionStartVal');
5675 if( f == 0 ) { f=150; } // default value
5676 oFrom = { opacity: 0, translateX: f };
5677 oTo = { opacity: 1, translateX: 0 };
5678 break;
5679 }
5680 case 'FADEIN':
5681 oFrom = { opacity: 0 };
5682 oTo = { opacity: 1 };
5683 break;
5684
5685
5686 }
5687
5688 var tweenable = new NGTweenable();
5689 tweenable.tween({
5690 from: oFrom,
5691 to: oTo,
5692 attachment: { $e:item.$elt, item: item, tw: tweenable },
5693 delay: delay,
5694 duration: G.tn.opt.Get('displayTransitionDuration'),
5695 easing: G.tn.opt.Get('displayTransitionEasing'),
5696 step: function (state, att) {
5697 window.requestAnimationFrame( function() {
5698 if( att.item.$elt === null ) { // the thumbnail may have been destroyed since the start of the animation
5699 att.tw.stop(false);
5700 return;
5701 }
5702 switch (G.tn.opt.Get('displayTransition')) {
5703 case 'RANDOMSCALE':
5704 att.$e.css( G.CSStransformName , 'scale(' + state.scale + ')').css('opacity', state.opacity);
5705 break;
5706 case 'SCALEUP':
5707 att.$e.css( G.CSStransformName , 'scale('+state.scale+')').css('opacity',state.opacity);
5708 break;
5709 case 'SCALEDOWN':
5710 att.item.$elt.last().css('opacity', state.opacity);
5711 att.item.CSSTransformSet('.nGY2GThumbnail', 'scale', state.scale);
5712 att.item.CSSTransformApply('.nGY2GThumbnail');
5713 break;
5714 case 'SLIDEUP':
5715 att.item.$elt.css('opacity', state.opacity);
5716 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px, '+state.translateY + 'px');
5717 att.item.CSSTransformApply('.nGY2GThumbnail');
5718 break;
5719 case 'SLIDEDOWN':
5720 att.item.$elt.css('opacity', state.opacity);
5721 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px,'+state.translateY+'px');
5722 att.item.CSSTransformApply('.nGY2GThumbnail');
5723 break;
5724 case 'FLIPUP':
5725 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px,'+state.translateY+'px');
5726 att.item.CSSTransformSet('.nGY2GThumbnail', 'rotateX', state.rotateX+'deg');
5727 att.item.$elt.css('opacity', state.opacity);
5728 att.item.CSSTransformApply('.nGY2GThumbnail');
5729 break;
5730 case 'FLIPDOWN':
5731 att.item.$elt.css('opacity', state.opacity);
5732 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px,' + state.translateY + 'px');
5733 att.item.CSSTransformSet('.nGY2GThumbnail', 'rotateX', state.rotateX + 'deg');
5734 att.item.CSSTransformApply('.nGY2GThumbnail');
5735 break;
5736 case 'SLIDEUP2':
5737 att.item.$elt.css('opacity', state.opacity);
5738 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px,' + state.translateY + 'px');
5739 att.item.CSSTransformSet('.nGY2GThumbnail', 'rotateY', state.rotateY + 'deg');
5740 att.item.CSSTransformApply('.nGY2GThumbnail');
5741 break;
5742 case 'IMAGESLIDEUP':
5743 att.item.$elt.css('opacity', state.opacity);
5744 att.item.$Elts['.nGY2GThumbnailImage'].css('top', state.top);
5745 break;
5746 case 'SLIDEDOWN2':
5747 att.item.$elt.css('opacity', state.opacity);
5748 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px, ' + state.translateY + 'px');
5749 att.item.CSSTransformSet('.nGY2GThumbnail', 'rotateY', state.rotateY + 'deg');
5750 att.item.CSSTransformApply('.nGY2GThumbnail');
5751 break;
5752 case 'SLIDERIGHT':
5753 att.item.$elt.css('opacity', state.opacity);
5754 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', state.translateX + 'px, 0px');
5755 att.item.CSSTransformApply('.nGY2GThumbnail');
5756 break;
5757 case 'SLIDELEFT':
5758 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', state.translateX + 'px, 0px');
5759 att.item.$elt.css('opacity', state.opacity);
5760 att.item.CSSTransformApply('.nGY2GThumbnail');
5761 break;
5762 case 'FADEIN':
5763 att.$e.css(state);
5764 break;
5765 }
5766 });
5767 // att.$e.css( G.CSStransformName , 'scale('+state.scale+')').css('opacity',state.opacity);
5768 },
5769 finish: function (state, att) {
5770 window.requestAnimationFrame( function() {
5771 if( att.item.$elt === null ) { return; }
5772
5773 switch (G.tn.opt.Get('displayTransition')) {
5774 case 'RANDOMSCALE':
5775 att.$e.css( G.CSStransformName , 'scale('+state.scale+')').css('opacity', '');
5776 break;
5777 case 'SCALEUP':
5778 att.$e.css( G.CSStransformName , '').css('opacity', '');
5779 break;
5780 case 'SCALEDOWN':
5781 att.item.$elt.last().css('opacity', '');
5782 att.item.CSSTransformSet('.nGY2GThumbnail', 'scale', state.scale);
5783 att.item.CSSTransformApply('.nGY2GThumbnail');
5784 break;
5785 case 'IMAGESLIDEUP':
5786 att.item.$elt.css('opacity', '');
5787 att.item.$Elts['.nGY2GThumbnailImage'].css('top', 0);
5788 break;
5789 case 'SLIDEDOWN2':
5790 att.item.$elt.css('opacity', '');
5791 att.item.CSSTransformApply('.nGY2GThumbnail');
5792 break;
5793 default :
5794 // case 'SLIDEUP':
5795 // case 'SLIDEDOWN':
5796 // case 'FLIPUP':
5797 // case 'FLIPDOWN':
5798 // case 'SLIDEUP2':
5799 // case 'SLIDERIGHT':
5800 // case 'SLIDELEFT':
5801 // case 'FADEIN':
5802 att.item.$elt.css('opacity', '');
5803 }
5804 ThumbnailAppearFinish(att.item);
5805 });
5806
5807 }
5808 });
5809
5810 }
5811
5812 // batch display thumbnails with animation
5813 function ThumbnailDisplayAnimBatch() {
5814
5815 G.GOM.thumbnails2Display.forEach( function(one) {
5816 ThumbnailDisplayAnim2(one.itm, one.d);
5817 });
5818 G.GOM.thumbnails2Display=[];
5819 }
5820
5821
5822
5823 // ######################################
5824 // Gallery display animation
5825 function GalleryAppear() {
5826
5827 var d=G.galleryDisplayTransitionDuration.Get();
5828 switch( G.galleryDisplayTransition.Get() ){
5829 case 'ROTATEX':
5830 G.$E.base.css({ perspective: '1000px', 'perspective-origin': '50% 0%' });
5831 new NGTweenable().tween({
5832 from: { r: 50 },
5833 to: { r: 0 },
5834 attachment: { orgIdx: G.GOM.albumIdx },
5835 duration: d,
5836 easing: 'easeOutCirc',
5837 step: function (state, att) {
5838 if( att.orgIdx == G.GOM.albumIdx ) {
5839 // window.ng_draf( function() {
5840 G.$E.conTnParent.css( G.CSStransformName , 'rotateX(' + state.r + 'deg)');
5841 // });
5842 }
5843 }
5844 });
5845 break;
5846 case 'SLIDEUP':
5847 G.$E.conTnParent.css({ opacity: 0 });
5848 new NGTweenable().tween({
5849 from: { y: 200, o: 0 },
5850 to: { y: 0, o: 1 },
5851 attachment: { orgIdx: G.GOM.albumIdx },
5852 duration: d,
5853 easing: 'easeOutCirc',
5854 step: function (state, att) {
5855 if( att.orgIdx == G.GOM.albumIdx ) {
5856 // window.ng_draf( function() {
5857 G.$E.conTnParent.css( G.CSStransformName , 'translate( 0px, '+state.y + 'px)').css('opacity', state.o);
5858 // });
5859 }
5860 }
5861 });
5862 break;
5863 case 'NONE':
5864 default:
5865 break;
5866 }
5867
5868
5869 }
5870
5871 // ######################################
5872 // ##### THUMBNAIL HOVER MANAGEMENT #####
5873 // ######################################
5874
5875 function ThumbnailOverInit( GOMidx ) {
5876 // Over init in 2 step:
5877 // 1) init with thumbnailBuildInit2 parameter
5878 // 2) init with the hover effect parameter
5879
5880
5881 var curTn = G.GOM.items[GOMidx];
5882 var item = G.I[curTn.thumbnailIdx];
5883
5884 if( item.$elt == null ) { return; } // zombie
5885
5886 var fu = G.O.fnThumbnailHoverInit;
5887 if( fu !== null ) {
5888 typeof fu == 'function' ? fu($e, item, GOMidx) : window[fu]($e, item, GOMidx);
5889 }
5890
5891 // build initialization
5892 var inits = G.tn.buildInit.get();
5893 for( var j = 0; j < inits.length; j++) {
5894 switch( inits[j].property ) {
5895 // CSS Transform
5896 case 'scale':
5897 case 'rotateX':
5898 case 'rotateY':
5899 case 'rotateZ':
5900 case 'translateX':
5901 case 'translateY':
5902 case 'translateZ':
5903 item.CSSTransformSet(inits[j].element, inits[j].property, inits[j].value);
5904 item.CSSTransformApply(inits[j].element);
5905 break;
5906 // CSS filter
5907 case 'blur':
5908 case 'brightness':
5909 case 'grayscale':
5910 case 'sepia':
5911 case 'contrast':
5912 case 'opacity':
5913 case 'saturate':
5914 item.CSSFilterSet(inits[j].element, inits[j].property, inits[j].value);
5915 item.CSSFilterApply(inits[j].element);
5916 break;
5917 default:
5918 var $t=item.$getElt(inits[j].element);
5919 $t.css( inits[j].property, inits[j].value );
5920 break;
5921 }
5922 }
5923
5924 // hover
5925 var effects = G.tn.hoverEffects.get();
5926 for( var j = 0; j < effects.length; j++) {
5927 if( effects[j].firstKeyframe === true ) {
5928 switch( effects[j].type ) {
5929 case 'scale':
5930 case 'rotateX':
5931 case 'rotateY':
5932 case 'rotateZ':
5933 case 'translateX':
5934 case 'translateY':
5935 case 'translateZ':
5936 item.CSSTransformSet(effects[j].element, effects[j].type, effects[j].from);
5937 item.CSSTransformApply(effects[j].element);
5938 break;
5939 case 'blur':
5940 case 'brightness':
5941 case 'grayscale':
5942 case 'sepia':
5943 case 'contrast':
5944 case 'opacity':
5945 case 'saturate':
5946 item.CSSFilterSet(effects[j].element, effects[j].type, effects[j].from);
5947 item.CSSFilterApply(effects[j].element);
5948 break;
5949 default:
5950 var $t = item.$getElt(effects[j].element);
5951 $t.css( effects[j].type, effects[j].from );
5952 break;
5953
5954 }
5955 }
5956 }
5957 item.hoverInitDone=true;
5958 }
5959
5960 function ThumbnailHoverReInitAll() {
5961 if( G.GOM.albumIdx == -1 ) { return; };
5962 var l = G.GOM.items.length;
5963 for( var i = 0; i < l ; i++ ) {
5964 ThumbnailOverInit(i);
5965 // G.GOM.items[i].hovered=false;
5966 G.I[G.GOM.items[i].thumbnailIdx].hovered = false;
5967 }
5968 }
5969
5970
5971 function ThumbnailHover( GOMidx ) {
5972 if( G.GOM.albumIdx == -1 || !G.galleryResizeEventEnabled ) { return; };
5973 if( G.GOM.slider.hostIdx == GOMidx ) {
5974 // slider hosted on thumbnail -> no hover effect
5975 return;
5976 }
5977 var curTn = G.GOM.items[GOMidx];
5978 var item = G.I[curTn.thumbnailIdx];
5979 if( item.kind == 'albumUp' || item.$elt == null ) { return; }
5980
5981 item.hovered = true;
5982
5983 var fu = G.O.fnThumbnailHover;
5984 if( fu !== null ) {
5985 typeof fu == 'function' ? fu(item.$elt, item, GOMidx) : window[fu](item.$elt, item, GOMidx);
5986 }
5987 var effects = G.tn.hoverEffects.get();
5988
5989 try {
5990 for( var j = 0; j < effects.length; j++) {
5991 if( effects[j].hoverin === true ) {
5992 //item.animate( effects[j], j*10, true );
5993 item.animate( effects[j], 0, true );
5994 }
5995 }
5996 // effects on whole layout
5997 // GalleryResize( GOMidx );
5998 }
5999 catch (e) {
6000 NanoAlert(G, 'error on hover: ' + e.message );
6001 }
6002
6003 }
6004
6005 function ThumbnailHoverOutAll() {
6006 if( G.GOM.albumIdx == -1 ) { return; };
6007 var l = G.GOM.items.length;
6008 for( var i = 0; i < l ; i++ ) {
6009 if( G.GOM.items[i].inDisplayArea ) {
6010 ThumbnailHoverOut(i);
6011 }
6012 else {
6013 G.I[G.GOM.items[i].thumbnailIdx].hovered = false;
6014 }
6015 }
6016 }
6017
6018
6019 function ThumbnailHoverOut( GOMidx ) {
6020 if( G.GOM.albumIdx == -1 || !G.galleryResizeEventEnabled ) { return; }
6021
6022 if( G.GOM.slider.hostIdx == GOMidx ) {
6023 // slider on thumbnail -> no hover effect
6024 return;
6025 }
6026
6027 var curTn = G.GOM.items[GOMidx];
6028 var item = G.I[curTn.thumbnailIdx];
6029 if( item.kind == 'albumUp' || !item.hovered ) { return; }
6030 item.hovered = false;
6031 if( item.$elt == null ) { return; }
6032
6033 var fu = G.O.fnThumbnailHoverOut;
6034 if( fu !== null ) {
6035 typeof fu == 'function' ? fu(item.$elt, item, GOMidx) : window[fu](item.$elt, item, GOMidx);
6036 }
6037
6038 var effects = G.tn.hoverEffects.get();
6039 try {
6040 for( var j = 0; j < effects.length; j++) {
6041 if( effects[j].hoverout === true ) {
6042 // item.animate( effects[j], j*10, false );
6043 item.animate( effects[j], 0, false );
6044 }
6045 }
6046 // effects on whole layout
6047 // GalleryResize( );
6048 }
6049 catch (e) {
6050 NanoAlert(G, 'error on hoverOut: ' + e.message );
6051 }
6052
6053 }
6054
6055
6056 /** @function DisplayPhoto */
6057 function DisplayPhoto( imageID, albumID ) {
6058
6059 if( G.O.debugMode ) { console.log('#DisplayPhoto : '+ albumID +'-'+ imageID); }
6060 var albumIdx = NGY2Item.GetIdx(G, albumID);
6061 if( albumIdx == 0 ) {
6062 G.GOM.curNavLevel = 'l1';
6063 }
6064 else {
6065 G.GOM.curNavLevel = 'lN';
6066 }
6067
6068 if( albumIdx == -1 ) {
6069 // get content of album on root level
6070 if( G.O.kind != '' ) {
6071 // do not add album if Markup or Javascript data
6072 NGY2Item.New( G, '', '', albumID, '0', 'album' ); // create empty album
6073 // albumIdx = G.I.length - 1;
6074 }
6075 }
6076
6077 var ngy2ItemIdx = NGY2Item.GetIdx(G, imageID);
6078 if( ngy2ItemIdx == -1 ) {
6079 // get content of the album
6080 AlbumGetContent( albumID, DisplayPhoto, imageID, albumID );
6081 return;
6082 }
6083
6084 if( G.O.debugMode ) { console.log('#DisplayPhoto : '+ ngy2ItemIdx); }
6085
6086 DisplayPhotoIdx(ngy2ItemIdx);
6087
6088 }
6089
6090 // function AlbumGetContent( albumIdx, fnToCall ) {
6091 function AlbumGetContent( albumID, fnToCall, fnParam1, fnParam2 ) {
6092 // var url='';
6093 // var kind='image';
6094 // var albumIdx=NGY2Item.GetIdx(G, albumID);
6095 // var photoIdx=NGY2Item.GetIdx(G, photoID);
6096
6097 switch( G.O.kind ) {
6098 // MARKUP / API
6099 case '':
6100 AlbumGetMarkupOrApi(fnToCall, fnParam1, fnParam2);
6101 break;
6102 // JSON, Flickr, Picasa, ...
6103 default:
6104 jQuery.nanogallery2['data_'+G.O.kind](G, 'AlbumGetContent', albumID, fnToCall, fnParam1, fnParam2 );
6105 }
6106
6107 }
6108
6109 var mediaList = {
6110 youtube : {
6111 getID: function( url ) {
6112 // https://stackoverflow.com/questions/10591547/how-to-get-youtube-video-id-from-url
6113 var s = url.match( /(?:https?:\/{2})?(?:w{3}\.)?youtu(?:be)?\.(?:com|be)(?:\/watch\?v=|\/)([^\s&]+)/ );
6114 return s != null ? s[1] : null;
6115 },
6116 thumbUrl: function( id ) {
6117 return 'https://img.youtube.com/vi/' + id + '/hqdefault.jpg';
6118 },
6119 url: function( id ) {
6120 return 'https://www.youtube.com/embed/' + id;
6121 },
6122 markup: function( id ) {
6123 // return '<iframe class="nGY2ViewerMedia" src="https://www.youtube.com/embed/' + id + '?rel=0" frameborder="0" gesture="media" allowfullscreen></iframe>';
6124 return '<iframe class="nGY2ViewerMedia" src="https://www.youtube.com/embed/' + id + '?rel=0" frameborder="0" allow="autoplay" allowfullscreen></iframe>';
6125 },
6126 kind: 'iframe'
6127 },
6128 vimeo : {
6129 getID: function( url ) {
6130 // https://stackoverflow.com/questions/2916544/parsing-a-vimeo-id-using-javascript
6131 // var s = url.match( /^.*(vimeo\.com\/)((channels\/[A-z]+\/)|(groups\/[A-z]+\/videos\/))?([0-9]+)/ );
6132 var s = url.match( /(http|https)?:\/\/(www\.)?vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/([^\/]*)\/videos\/|)(\d+)(?:|\/\?)/ );
6133 return s != null ? s[4] : null;
6134 },
6135 url: function( id ) {
6136 return 'https://player.vimeo.com/video/' + id;
6137 },
6138 markup: function( id ) {
6139 // return '<iframe class="nGY2ViewerMedia" src="https://player.vimeo.com/video/' + id + '?rel=0" frameborder="0" gesture="media" allowfullscreen></iframe>';
6140 // return '<iframe class="nGY2ViewerMedia" src="https://player.vimeo.com/video/' + id + '?rel=0" frameborder="0" allow="autoplay; fullscreen" allowfullscreen></iframe>';
6141 return '<iframe class="nGY2ViewerMedia" src="https://player.vimeo.com/video/' + id + '" frameborder="0" allow="autoplay; fullscreen" allowfullscreen></iframe>';
6142 },
6143 kind: 'iframe'
6144 },
6145 dailymotion : {
6146 getID: function( url ) {
6147 // https://stackoverflow.com/questions/12387389/how-to-parse-dailymotion-video-url-in-javascript
6148 var m = url.match(/^.+dailymotion.com\/(video|hub)\/([^_]+)[^#]*(#video=([^_&]+))?/);
6149 if (m !== null) {
6150 if(m[4] !== undefined) {
6151 return m[4];
6152 }
6153 return m[2];
6154 }
6155 return null;
6156 },
6157 thumbUrl: function( id ) {
6158 return 'https://www.dailymotion.com/thumbnail/video/' + id;
6159 },
6160 url: function( id ) {
6161 return 'https://www.dailymotion.com/embed/video/' + id;
6162 },
6163 markup: function( id ) {
6164 // return '<iframe class="nGY2ViewerMedia" src="https://www.dailymotion.com/embed/video/' + id + '?rel=0" frameborder="0" gesture="media" allowfullscreen></iframe>';
6165 return '<iframe class="nGY2ViewerMedia" src="https://www.dailymotion.com/embed/video/' + id + '?rel=0" frameborder="0" allow="autoplay" allowfullscreen></iframe>';
6166 },
6167 kind: 'iframe'
6168 },
6169 selfhosted : {
6170 // SELF-HOSTED VIDEOS
6171 getID: function( url ) {
6172 // In order to leave things as is, I used ID to identify the extension
6173 // https://stackoverflow.com/questions/6997262/how-to-pull-url-file-extension-out-of-url-string-using-javascript
6174 // Make sure the method used for verifying the extension matches the kind of url your selfhosted video has
6175 var extension = url.split('.').pop();
6176
6177 // supported extensions
6178 var s = ( extension === 'mp4' || extension === 'webm' || extension === 'ogv' || extension === '3gp' ) ? extension : null ;
6179 return s;
6180 },
6181 markup: function( url ) {
6182 // return '<video controls class="nGY2ViewerMedia"><source src="${id.src}" type="video/${id.type}" preload="auto">Your browser does not support the video tag (HTML 5).</video>';
6183 var extension = url.split('.').pop();
6184 return '<video controls class="nGY2ViewerMedia"><source src="'+ url +'" type="video/'+ extension +'" preload="auto">Your browser does not support the video tag (HTML 5).</video>';
6185 },
6186 kind: 'video',
6187 selfhosted : true
6188 }
6189 };
6190
6191 function AlbumGetMarkupOrApi ( fnToCall, fnParam1, fnParam2 ) {
6192
6193 if( G.markupOrApiProcessed === true ) {
6194 // already processed (maybe location hash to unknow reference) -> display root album
6195 DisplayAlbum('-1', 0);
6196 return;
6197 }
6198
6199 if( G.O.items !== undefined && G.O.items !== null ) {
6200 // data defined as an object in an option parameter
6201 GetContentApiObject();
6202 }
6203 else {
6204 if( G.O.$markup.length > 0 ) {
6205 // data defined as markup (href elements)
6206 GetContentMarkup( G.O.$markup );
6207 G.O.$markup=[] ;
6208 }
6209 else {
6210 NanoConsoleLog(G, 'error: no media to process.');
6211 return;
6212 }
6213 }
6214
6215 G.markupOrApiProcessed = true;
6216 if( fnToCall !== null && fnToCall !== undefined) {
6217 fnToCall( fnParam1, fnParam2, null );
6218 }
6219 }
6220
6221 function StartsWithProtocol ( path ) {
6222 if( path == undefined ) { return false; }
6223 // if( path == null ) { return false; }
6224
6225 var pattern = /^((http|https|ftp|ftps|file):\/\/)/;
6226 if( !pattern.test(path) ) {
6227 // not a full URL
6228 return false;
6229 }
6230 return true;
6231 }
6232
6233 function GetContentApiObject() {
6234 var foundAlbumID=false;
6235 var nbTitles = 0;
6236 var AlbumPostProcess = NGY2Tools.AlbumPostProcess.bind(G);
6237
6238 G.I[0].contentIsLoaded = true;
6239
6240 jQuery.each(G.O.items, function(i,item){
6241
6242 var title = '';
6243 title = GetI18nItem(item, 'title');
6244 if( title === undefined ) { title=''; }
6245
6246 var src='';
6247 if( item['src'+RetrieveCurWidth().toUpperCase()] !== undefined ) {
6248 src = item['src'+RetrieveCurWidth().toUpperCase()];
6249 }
6250 else {
6251 src = item.src;
6252 }
6253 if( !StartsWithProtocol(src) ) {
6254 src = G.O.itemsBaseURL + src;
6255 }
6256
6257 var thumbsrc = '';
6258 if( item.srct !== undefined && item.srct.length > 0 ) {
6259 thumbsrc = item.srct;
6260 if( !StartsWithProtocol(thumbsrc) ) {
6261 thumbsrc = G.O.itemsBaseURL + thumbsrc;
6262 }
6263 }
6264 else {
6265 thumbsrc = src;
6266 }
6267
6268 if( G.O.thumbnailLabel.get('title') != '' ) {
6269 title = GetImageTitle(src);
6270 }
6271
6272 var description=''; //'&nbsp;';
6273 description=GetI18nItem(item,'description');
6274 if( description === undefined ) { description=''; }
6275 //if( toType(item.description) == 'string' ) {
6276 // description=item.description;
6277 //}
6278
6279 var tags = GetI18nItem(item, 'tags');
6280 if( tags === undefined ) { tags=''; }
6281
6282 var albumID = 0;
6283 if( item.albumID !== undefined ) {
6284 albumID=item.albumID;
6285 foundAlbumID = true;
6286 }
6287 var ID = null;
6288 if( item.ID !== undefined ) {
6289 ID = item.ID;
6290 }
6291 var kind = 'image';
6292 if( item.kind !== undefined && item.kind.length > 0 ) {
6293 kind = item.kind;
6294 }
6295
6296 var newItem=NGY2Item.New( G, title, description, ID, albumID, kind, tags );
6297 if( title != '' ) {
6298 nbTitles++;
6299 }
6300
6301 // media source url - img is the default media kind
6302 newItem.setMediaURL( src, 'img');
6303debugger;
6304 // manage media kinds other than IMG
6305 jQuery.each(mediaList, function ( n, media ) {
6306 var id = media.getID(src);
6307 if( id != null ) {
6308 if( typeof media.url == 'function' ) { src = media.url(id); }
6309 if( typeof media.thumbUrl == 'function' ) { thumbsrc = media.thumbUrl(id); }
6310 newItem.mediaKind = media.kind;
6311 newItem.mediaMarkup = ( media.selfhosted ) ? media.markup( src ) : media.markup(id);
6312 return false;
6313 }
6314 });
6315
6316 // image size
6317 if( item.imageWidth !== undefined ) { newItem.imageWidth = item.width; }
6318 if( item.imageHeight !== undefined ) { newItem.imageHeight = item.height; }
6319
6320 // THUMBNAILS
6321
6322 // thumbnail image size
6323 var tw = item.imgtWidth !== undefined ? item.imgtWidth : 0;
6324 var th = item.imgtHeight !== undefined ? item.imgtHeight : 0;
6325
6326 // default thumbnail URL and size
6327 newItem.thumbs = {
6328 url: { l1 : { xs: thumbsrc, sm: thumbsrc, me: thumbsrc, la: thumbsrc, xl: thumbsrc }, lN : { xs: thumbsrc, sm: thumbsrc, me: thumbsrc, la: thumbsrc, xl: thumbsrc } },
6329 width: { l1 : { xs: tw, sm: tw, me: tw, la: tw, xl: tw }, lN : { xs: tw, sm: tw, me: tw, la: tw, xl: tw } },
6330 height: { l1 : { xs: th, sm: th, me: th, la: th, xl: th }, lN : { xs: th, sm: th, me: th, la: th, xl: th } }
6331 };
6332
6333 // default media type -> IMG
6334 if( newItem.mediaKind == 'img' ) {
6335
6336 // responsive thumbnails URL and size
6337 var lst=['xs', 'sm', 'me', 'la', 'xl'];
6338 for( var i=0; i< lst.length; i++ ) {
6339 // url
6340 var turl = item['srct' + lst[i].toUpperCase()];
6341 if( turl !== undefined ) {
6342 if( !StartsWithProtocol(turl) ) {
6343 turl = G.O.itemsBaseURL + turl;
6344 }
6345 newItem.url.l1[lst[i]] = turl;
6346 newItem.url.lN[lst[i]] = turl;
6347 }
6348 // width
6349 var tw = item['imgt' + lst[i].toUpperCase() + 'Width'];
6350 if( tw != undefined ) {
6351 newItem.width.l1[lst[i]] = parseInt(tw);
6352 newItem.width.lN[lst[i]] = parseInt(tw);
6353 }
6354 // height
6355 var th = item['imgt' + lst[i].toUpperCase() + 'Height'];
6356 if( th != undefined ) {
6357 newItem.height.l1[lst[i]] = parseInt(th);
6358 newItem.height.lN[lst[i]] = parseInt(th);
6359 }
6360 }
6361 }
6362
6363 // dominant colors (needs to be a base64 gif)
6364 if( item.imageDominantColors !== undefined ) {
6365 newItem.imageDominantColors = item.imageDominantColors;
6366 }
6367 // dominant color (rgb hex)
6368 if( item.imageDominantColor !== undefined ) {
6369 newItem.imageDominantColor = item.imageDominantColor;
6370 }
6371
6372 // dest url
6373 if( item.destURL !== undefined && item.destURL.length>0 ) {
6374 newItem.destinationURL = item.destURL;
6375 }
6376
6377 // download image url
6378 if( item.downloadURL !== undefined && item.downloadURL.length>0 ) {
6379 newItem.downloadURL = item.downloadURL;
6380 }
6381
6382 // EXIF DATA
6383 // Exif - model
6384 if( item.exifModel !== undefined ) { newItem.exif.model = item.exifModel; }
6385 // Exif - flash
6386 if( item.exifFlash !== undefined ) { newItem.exif.flash = item.exifFlash; }
6387 // Exif - focallength
6388 if( item.exifFocalLength !== undefined ) { newItem.exif.focallength = item.exifFocalLength; }
6389 // Exif - fstop
6390 if( item.exifFStop !== undefined ) { newItem.exif.fstop = item.exifFStop; }
6391 // Exif - exposure
6392 if( item.exifExposure !== undefined ) { newItem.exif.exposure = item.exifExposure; }
6393 // Exif - time
6394 if( item.exifIso !== undefined ) { newItem.exif.iso = item.exifIso; }
6395 // Exif - iso
6396 if( item.exifTime !== undefined ) { newItem.exif.time = item.exifTime; }
6397 // Exif - location
6398 if( item.exifLocation !== undefined ) { newItem.exif.location = item.exifLocation; }
6399
6400
6401 // custom data
6402 if( item.customData !== null ) {
6403 newItem.customData = cloneJSObject( item.customData );
6404 }
6405
6406 newItem.contentIsLoaded = true;
6407
6408 var fu = G.O.fnProcessData;
6409 if( fu !== null ) {
6410 typeof fu == 'function' ? fu(newItem, 'api', item) : window[fu](newItem, 'api', item);
6411 }
6412
6413 AlbumPostProcess(albumID);
6414 });
6415
6416 // if( foundAlbumID ) { G.O.displayBreadcrumb=true; }
6417 if( nbTitles == 0 ) { G.O.thumbnailLabel.display=false; }
6418
6419 }
6420
6421
6422 // Returns the text of the DOM element (without children)
6423 // Because jQuery().text() returns the text of all children
6424 function ElementGetText( element ) {
6425
6426 var text = '';
6427 if( element.childNodes[0] !== undefined ) {
6428 if( element.childNodes[0].nodeValue !== null && element.childNodes[0].nodeValue !== undefined ) {
6429 text = element.childNodes[0].nodeValue.trim();
6430 }
6431 }
6432 return text;
6433 }
6434
6435 // Extract items from the jQuery elements
6436 function GetContentMarkup( $elements, group ) {
6437 var foundAlbumID = false;
6438 var nbTitles = 0;
6439 var AlbumPostProcess = NGY2Tools.AlbumPostProcess.bind(G);
6440 var GetImageTitleFromURL = NGY2Tools.GetImageTitleFromURL.bind(G);
6441
6442 G.I[0].contentIsLoaded = true;
6443
6444 jQuery.each($elements, function(i, item){
6445
6446 // compare to group defined on the element that has been clicked (lightbox standalone)
6447 if( item.dataset.nanogallery2Lgroup != group ) { return; }
6448
6449 // ignore element <SCRIPT>
6450 if( item.nodeName == 'SCRIPT' ) { return; }
6451
6452 // create dictionnary with all data attribute name in lowercase (to be case unsensitive)
6453 var data = {
6454 // all possible data attributes with some default values
6455 'data-ngdesc': '', // item description
6456 'data-ngid': null, // ID
6457 'data-ngkind': 'image', // kind (image, album, albumup)
6458 'data-ngtags': null, // tags
6459 'data-ngdest': '', // destination URL
6460 'data-ngthumbimgwidth': 0, // thumbnail width
6461 'data-ngthumbimgheight': 0, // thumbnail height
6462 'data-ngimagewidth': 0, // image width
6463 'data-ngimageheight': 0, // image height
6464 'data-ngimagedominantcolors': null, // image dominant colors
6465 'data-ngimagedominantcolor': null, // image dominant colors
6466 'data-ngexifmodel': '', // EXIF data
6467 'data-ngexifflash': '',
6468 'data-ngexiffocallength': '',
6469 'data-ngexiffstop': '',
6470 'data-ngexifexposure': '',
6471 'data-ngexifiso': '',
6472 'data-ngexiftime': '',
6473 'data-ngexiflocation': '',
6474 'data-ngsrc': '',
6475 'alt': ''
6476 };
6477
6478 // Extract data attributes from main item
6479 [].forEach.call( item.attributes, function(attr) {
6480 data[attr.name.toLowerCase()] = attr.value.trim();
6481 });
6482
6483 var title = ElementGetText(item);
6484 if( title == '' && data.alt != '') {
6485 // no title -> check ALT attribute of main element
6486 title = data['alt'];
6487 }
6488
6489 // Complete with data attributes from all children
6490 jQuery.each($(item).children(), function(i, sub_item){
6491
6492 // title may be set on a child element
6493 if( title == '' ) {
6494 title = ElementGetText(sub_item);
6495 }
6496
6497 [].forEach.call( sub_item.attributes, function(attr) {
6498 data[attr.name.toLowerCase()] = attr.value.trim();
6499 });
6500
6501 if( title == '' && data.alt != '') {
6502 // no title -> check ALT attribute of sub element
6503 title = data['alt'];
6504 }
6505
6506 });
6507
6508 // BIG IMAGE URL
6509 // responsive image URL
6510 var src = '',
6511 st = RetrieveCurWidth().toUpperCase();
6512 if( data.hasOwnProperty('data-ngsrc'+st) ) {
6513 src = data['data-ngsrc'+st];
6514 }
6515 // image URL from data-ngsrc attribute
6516 // if( src == '' ) {
6517 // src = data['data-ngsrc'];
6518 // }
6519 // image URL from href attribute (a element)
6520 // if( src == '' ) {
6521 // src = data['href'];
6522 // }
6523 src = src || data['data-ngsrc'] || data['href'];
6524 if( src !== undefined && !StartsWithProtocol(src) ) { // do not add the base URL if src starts with a protocol (http, https...)
6525 src = G.O.itemsBaseURL + src;
6526 }
6527
6528
6529 // THUMBNAIL IMAGE
6530 var thumbsrc = '';
6531 // src attribute (img element)
6532 if( data.hasOwnProperty('src') ) {
6533 thumbsrc = data['src'];
6534 }
6535 // data-ngthumb attribute
6536 if( thumbsrc == '' && data.hasOwnProperty('data-ngthumb') ) {
6537 thumbsrc = data['data-ngthumb'];
6538 }
6539 if( thumbsrc == '' ) {
6540 thumbsrc = src; // no thumbnail image URL -> use big image URL
6541 }
6542 if( thumbsrc !== undefined && !StartsWithProtocol(thumbsrc) ) {
6543 thumbsrc = G.O.itemsBaseURL + thumbsrc;
6544 }
6545
6546 // ignore if no media URL at all
6547 if( src === undefined && thumbsrc === undefined ) { return; }
6548
6549
6550 //newObj.description=jQuery(item).attr('data-ngdesc');
6551 var description = data['data-ngdesc'];
6552 var ID = data['id'] || data['data-ngid'];
6553 // if( ID == undefined ) {
6554 // ID = data['data-ngid'];
6555 // }
6556 var kind = data['data-ngkind'];
6557 var tags = data['data-ngtags'];
6558
6559 var albumID = '0';
6560 if( data.hasOwnProperty('data-ngalbumid') ) {
6561 albumID = data['data-ngalbumid'];
6562 foundAlbumID = true;
6563 }
6564
6565 // var title = jQuery(item).text();
6566 var title_from_url = GetImageTitleFromURL( src );
6567 if( title_from_url != '' ) {
6568 title = title_from_url;
6569 }
6570
6571 var newItem = NGY2Item.New( G, title, description, ID, albumID, kind, tags );
6572 if( title != '' ) {
6573 nbTitles++;
6574 }
6575
6576 // media source url - img is the default media kind
6577 newItem.setMediaURL( src, 'img');
6578
6579 // manage media kinds other than IMG
6580 // newItem.mediaKind = 'img';
6581 jQuery.each(mediaList, function ( n, media ) {
6582 var id = media.getID(src);
6583 if( id != null ) {
6584 if( typeof media.url == 'function' ) { src = media.url(id); }
6585 if( typeof media.thumbUrl == 'function' ) { thumbsrc = media.thumbUrl(id); }
6586 newItem.mediaKind = media.kind;
6587 newItem.mediaMarkup = ( media.selfhosted ) ? media.markup( src ) : media.markup(id);
6588 return false;
6589 }
6590 });
6591
6592
6593 // Big image size
6594 newItem.imageWidth = parseInt( data['data-ngimagewidth'] );
6595 newItem.imageHeight = parseInt( data['data-ngimageheight'] );
6596
6597 // default thumbnail image URL and size
6598 var tw = parseInt(data['data-ngthumbimgwidth']);
6599 var th = parseInt(data['data-ngthumbimgheight']);
6600 newItem.thumbs = {
6601 url: { l1 : { xs: thumbsrc, sm: thumbsrc, me: thumbsrc, la: thumbsrc, xl: thumbsrc }, lN : { xs: thumbsrc, sm: thumbsrc, me: thumbsrc, la: thumbsrc, xl: thumbsrc } },
6602 width: { l1 : { xs: tw, sm: tw, me: tw, la: tw, xl: tw }, lN : { xs: tw, sm: tw, me: tw, la: tw, xl: tw } },
6603 height: { l1 : { xs: th, sm: th, me: th, la: th, xl: th }, lN : { xs: th, sm: th, me: th, la: th, xl: th } }
6604 };
6605
6606 // Media type -> IMG
6607 if( newItem.mediaKind == 'img' ) {
6608
6609 // responsive thumbnails URL and size
6610 var lst = ['xs', 'sm', 'me', 'la', 'xl'];
6611 for( var i = 0; i < lst.length; i++ ) {
6612 // url
6613 if( data.hasOwnProperty('data-ngthumb' + lst[i]) ) {
6614 var turl=data['data-ngthumb' + lst[i]];
6615 if( !StartsWithProtocol(turl) ) {
6616 turl = G.O.itemsBaseURL + turl;
6617 }
6618 newItem.url.l1[lst[i]] = turl;
6619 newItem.url.lN[lst[i]] = turl;
6620 }
6621
6622 // width
6623 if( data.hasOwnProperty('data-ngthumb' + lst[i] + 'width') ) {
6624 var tw=parseInt(data['data-ngthumb' + lst[i] + 'width']);
6625 newItem.width.l1[lst[i]] = tw;
6626 newItem.width.lN[lst[i]] = tw;
6627 }
6628 // height
6629 if( data.hasOwnProperty('data-ngthumb' + lst[i] + 'height') ) {
6630 var th=parseInt('data-ngthumb' + lst[i] + 'height');
6631 newItem.height.l1[lst[i]] = th;
6632 newItem.height.lN[lst[i]] = th;
6633 }
6634 }
6635 }
6636
6637
6638 // dominant colorS (needs to be a base64 gif)
6639 newItem.imageDominantColors = data['data-ngimagedominantcolors'];
6640 // dominant color (rgb hex)
6641 newItem.imageDominantColor = data['data-ngimagedominantcolors'];
6642
6643 newItem.destinationURL = data['data-ngdest'];
6644 newItem.downloadURL = data['data-ngdownloadurl'];
6645
6646 // Exif - model
6647 newItem.exif.model=data['data-ngexifmodel'];
6648 // Exif - flash
6649 newItem.exif.flash=data['data-ngexifflash'];
6650 // Exif - focallength
6651 newItem.exif.focallength=data['data-ngexiffocallength'];
6652 // Exif - fstop
6653 newItem.exif.fstop=data['data-ngexiffstop'];
6654 // Exif - exposure
6655 newItem.exif.exposure=data['data-ngexifexposure'];
6656 // Exif - iso
6657 newItem.exif.iso=data['data-ngexifiso'];
6658 // Exif - time
6659 newItem.exif.time=data['data-ngexiftime'];
6660 // Exif - location
6661 newItem.exif.location=data['data-ngexiflocation'];
6662
6663 newItem.contentIsLoaded = true;
6664
6665 // custom data
6666 if( jQuery(item).data('customdata') !== undefined ) {
6667 newItem.customData = cloneJSObject(jQuery(item).data('customdata'));
6668 }
6669 // custom data
6670 if( jQuery(item).data('ngcustomdata') !== undefined ) {
6671 newItem.customData = cloneJSObject(jQuery(item).data('ngcustomdata'));
6672 }
6673
6674 var fu=G.O.fnProcessData;
6675 if( fu !== null ) {
6676 typeof fu == 'function' ? fu(newItem, 'markup', item) : window[fu](newItem, 'markup', item);
6677 }
6678
6679 AlbumPostProcess(albumID);
6680
6681 });
6682
6683 // if( foundAlbumID ) { G.O.displayBreadcrumb=true; }
6684 if( nbTitles == 0 ) { G.O.thumbnailLabel.display = false; }
6685
6686 }
6687
6688
6689 // ################################
6690 // ##### DEFINE VARIABLES #####
6691 // ################################
6692
6693
6694 /** @function DefineVariables */
6695 function DefineVariables() {
6696
6697 // change 'picasa' to 'google' for compatibility reason
6698 if( G.O.kind.toUpperCase() == 'PICASA' || G.O.kind.toUpperCase() == 'GOOGLE') {
6699 G.O.kind='google2';
6700 }
6701
6702 // management of screen width
6703 G.GOM.cache.viewport = getViewport();
6704 G.GOM.curWidth = RetrieveCurWidth();
6705
6706 // tumbnail toolbar
6707 jQuery.extend(true, G.tn.toolbar.image, G.O.thumbnailToolbarImage );
6708 jQuery.extend(true, G.tn.toolbar.album, G.O.thumbnailToolbarAlbum );
6709 var t = ['image', 'album'];
6710 var pos= ['topLeft', 'topRight', 'bottomLeft', 'bottomRight']
6711 for( var i=0; i < t.length ; i++ ) {
6712 for( var j=0; j < pos.length ; j++ ) {
6713 G.tn.toolbar[t[i]][pos[j]] = G.tn.toolbar[t[i]][pos[j]].toUpperCase();
6714 }
6715 }
6716
6717 // convert label settings
6718 if( G.O.thumbnailLabel.position == 'overImageOnBottom' ) {
6719 G.O.thumbnailLabel.valign = 'bottom';
6720 G.O.thumbnailLabel.position = 'overImage';
6721 }
6722 if( G.O.thumbnailLabel.position == 'overImageOnMiddle' ) {
6723 G.O.thumbnailLabel.valign = 'middle';
6724 G.O.thumbnailLabel.position = 'overImage';
6725 }
6726 if( G.O.thumbnailLabel.position == 'overImageOnTop' ) {
6727 G.O.thumbnailLabel.valign = 'top';
6728 G.O.thumbnailLabel.position = 'overImage';
6729 }
6730 if( G.O.thumbnailL1Label !== undefined && G.O.thumbnailL1Label.position !== undefined ) {
6731 if( G.O.thumbnailL1Label.position == 'overImageOnBottom' ) {
6732 G.O.thumbnailL1Label.valign = 'bottom';
6733 G.O.thumbnailL1Label.position = 'overImage';
6734 }
6735 if( G.O.thumbnailL1Label.position == 'overImageOnMiddle' ) {
6736 G.O.thumbnailL1Label.valign = 'middle';
6737 G.O.thumbnailL1Label.position = 'overImage';
6738 }
6739 if( G.O.thumbnailL1Label.position == 'overImageOnTop' ) {
6740 G.O.thumbnailL1Label.valign = 'top';
6741 G.O.thumbnailL1Label.position = 'overImage';
6742 }
6743 }
6744
6745 // thumbnails label - level dependant settings
6746 G.O.thumbnailLabel.get = function( opt ) {
6747 if( G.GOM.curNavLevel == 'l1' && G.O.thumbnailL1Label !== undefined && G.O.thumbnailL1Label[opt] !== undefined ) {
6748 return G.O.thumbnailL1Label[opt];
6749 }
6750 else {
6751 return G.O.thumbnailLabel[opt];
6752 }
6753 };
6754 G.O.thumbnailLabel.set = function( opt, value ) {
6755 if( G.GOM.curNavLevel == 'l1' && G.O.thumbnailL1Label !== undefined && G.O.thumbnailL1Label[opt] !== undefined ) {
6756 G.O.thumbnailL1Label[opt]=value;
6757 }
6758 else {
6759 G.O.thumbnailLabel[opt]=value;
6760 }
6761 };
6762
6763 if( G.O.blockList != '' ) { G.blockList = G.O.blockList.toUpperCase().split('|'); }
6764 if( G.O.allowList != '' ) { G.allowList = G.O.allowList.toUpperCase().split('|'); }
6765
6766 if( G.O.albumList2 !== undefined && G.O.albumList2 !== null && G.O.albumList2.constructor === Array ) {
6767 var l=G.O.albumList2.length;
6768 for(var i=0; i< l; i++ ) {
6769 G.albumList.push(G.O.albumList2[i]);
6770 }
6771 // G.albumList=G.O.albumList.toUpperCase().split('|');
6772 }
6773 if( G.O.albumList2 !== undefined && typeof G.O.albumList2 == 'string' ) {
6774 G.albumList.push(G.O.albumList2);
6775 }
6776
6777
6778 // thumbnail image crop
6779 G.tn.opt.lN.crop = G.O.thumbnailCrop;
6780 G.tn.opt.l1.crop = G.O.thumbnailL1Crop != null ? G.O.thumbnailL1Crop : G.O.thumbnailCrop;
6781
6782
6783 function ThumbnailOpt( lN, l1, opt) {
6784 G.tn.opt.lN[opt] = G.O[lN];
6785 G.tn.opt.l1[opt] = G.O[lN];
6786 if( toType(G.O[l1]) == 'number' ) {
6787 G.tn.opt.l1[opt] = G.O[l1];
6788 }
6789 }
6790 // thumbnail stacks
6791 ThumbnailOpt('thumbnailStacks', 'thumbnailL1Stacks', 'stacks');
6792 // thumbnail stacks translate X
6793 ThumbnailOpt('thumbnailStacksTranslateX', 'thumbnailL1StacksTranslateX', 'stacksTranslateX');
6794 // thumbnail stacks translate Y
6795 ThumbnailOpt('thumbnailStacksTranslateY', 'thumbnailL1StacksTranslateY', 'stacksTranslateY');
6796 // thumbnail stacks translate Z
6797 ThumbnailOpt('thumbnailStacksTranslateZ', 'thumbnailL1StacksTranslateZ', 'stacksTranslateZ');
6798 // thumbnail stacks rotate X
6799 ThumbnailOpt('thumbnailStacksRotateX', 'thumbnailL1StacksRotateX', 'stacksRotateX');
6800 // thumbnail stacks rotate Y
6801 ThumbnailOpt('thumbnailStacksRotateY', 'thumbnailL1StacksRotateY', 'stacksRotateY');
6802 // thumbnail stacks rotate Z
6803 ThumbnailOpt('thumbnailStacksRotateZ', 'thumbnailL1StacksRotateZ', 'stacksRotateZ');
6804 // thumbnail stacks scale
6805 ThumbnailOpt('thumbnailStacksScale', 'thumbnailL1StacksScale', 'stacksScale');
6806 // thumbnail gutter width
6807 // ThumbnailOpt('thumbnailGutterWidth', 'thumbnailL1GutterWidth', 'gutterWidth');
6808 // thumbnail gutter height
6809 // ThumbnailOpt('thumbnailGutterHeight', 'thumbnailL1GutterHeight', 'gutterHeight');
6810 // thumbnail border horizontal
6811 ThumbnailOpt('thumbnailBorderHorizontal', 'thumbnailL1BorderHorizontal', 'borderHorizontal');
6812 // thumbnail border vertical
6813 ThumbnailOpt('thumbnailBorderVertical', 'thumbnailL1BorderVertical', 'borderVertical');
6814 // thumbnail grid base height (for cascading layout)
6815 ThumbnailOpt('thumbnailBaseGridHeight', 'thumbnailL1BaseGridHeight', 'baseGridHeight');
6816
6817
6818 // Set same value to all widths
6819 function ResponsiveSetSize( setting, level, v ) {
6820 G.tn.settings[setting][level]['xs'] = v;
6821 G.tn.settings[setting][level]['sm'] = v;
6822 G.tn.settings[setting][level]['me'] = v;
6823 G.tn.settings[setting][level]['la'] = v;
6824 G.tn.settings[setting][level]['xl'] = v;
6825 }
6826
6827 // Get and evaluate responsive values from one option
6828 // Responsive is with syntax: n XSn1 SMn2 MEn3 LAn4 XLn5 (where n is the default value)
6829 // Value 'auto' is accepted for all options, but is handeld only for thumbnail width/height
6830 function ResponsiveOption( option, setting, level ) {
6831 var v = G.O[option];
6832
6833 if( v === undefined || v === null ) { return; }
6834
6835 // if( toType(v) == 'number' ) {
6836 if( toType(v) == 'number' || v.indexOf(' ') == -1 ) {
6837 // set value for all widths
6838 var vn = 'auto';
6839 if( v != 'auto' ) { vn = parseInt(v); }
6840 ResponsiveSetSize( setting, level, vn );
6841 }
6842 else {
6843 var sp = v.split(' ');
6844 if( sp.length > 0 && +sp[0] === +sp[0] ) { // check if sp[0] is a number
6845 // first value is the default size for all widths
6846 var vn = 'auto';
6847 if( sp[0] != 'auto' ) { vn = parseInt(sp[0]); }
6848 ResponsiveSetSize( setting, level, vn );
6849 }
6850 for( var i = 1; i < sp.length; i++ ) {
6851 if( /^xs|sm|me|la|xl/i.test( sp[i] ) ) { // regex: i ignores the case and ^ means "starts with"
6852 var wi = sp[i].substring(0, 2).toLowerCase();
6853 var va = sp[i].substring(2);
6854 var vn = 'auto';
6855 if( va != 'auto' ) { vn = parseInt(va); }
6856 G.tn.settings[setting][level][wi] = vn;
6857 }
6858 }
6859 }
6860 }
6861
6862 ResponsiveOption('thumbnailGutterWidth', 'gutterWidth', 'lN');
6863 ResponsiveOption('thumbnailGutterWidth', 'gutterWidth', 'l1'); // set default values for first level
6864 ResponsiveOption('thumbnailL1GutterWidth', 'gutterWidth', 'l1');
6865 ResponsiveOption('thumbnailGutterHeight', 'gutterHeight', 'lN');
6866 ResponsiveOption('thumbnailGutterHeight', 'gutterHeight', 'l1'); // set default values for first level
6867 ResponsiveOption('thumbnailL1GutterHeight', 'gutterHeight', 'l1');
6868
6869 // gallery display mode
6870 G.galleryDisplayMode.lN = G.O.galleryDisplayMode.toUpperCase();
6871 G.galleryDisplayMode.l1 = G.O.galleryL1DisplayMode != null ? G.O.galleryL1DisplayMode.toUpperCase() : G.O.galleryDisplayMode.toUpperCase();
6872
6873 // gallery maximum number of lines of thumbnails
6874 G.galleryMaxRows.lN = G.O.galleryMaxRows;
6875 G.galleryMaxRows.l1 = toType(G.O.galleryL1MaxRows) == 'number' ? G.O.galleryL1MaxRows : G.O.galleryMaxRows;
6876
6877 // gallery last row full
6878 G.galleryLastRowFull.lN = G.O.galleryLastRowFull;
6879 G.galleryLastRowFull.l1 = G.O.galleryL1LastRowFull != null ? G.O.galleryL1LastRowFull : G.O.galleryLastRowFull;
6880
6881 // gallery sorting
6882 G.gallerySorting.lN = G.O.gallerySorting.toUpperCase();
6883 G.gallerySorting.l1 = G.O.galleryL1Sorting != null ? G.O.galleryL1Sorting.toUpperCase() : G.gallerySorting.lN;
6884
6885 // gallery display transition
6886 G.galleryDisplayTransition.lN = G.O.galleryDisplayTransition.toUpperCase();
6887 G.galleryDisplayTransition.l1 = G.O.galleryL1DisplayTransition != null ? G.O.galleryL1DisplayTransition.toUpperCase() : G.galleryDisplayTransition.lN;
6888
6889 // gallery display transition duration
6890 G.galleryDisplayTransitionDuration.lN = G.O.galleryDisplayTransitionDuration;
6891 G.galleryDisplayTransitionDuration.l1 = G.O.galleryL1DisplayTransitionDuration != null ? G.O.galleryL1DisplayTransitionDuration : G.galleryDisplayTransitionDuration.lN;
6892
6893 // gallery max items per album (not for inline/api defined items)
6894 G.galleryMaxItems.lN = G.O.galleryMaxItems;
6895 G.galleryMaxItems.l1 = toType(G.O.galleryL1MaxItems) == 'number' ? G.O.galleryL1MaxItems : G.O.galleryMaxItems;
6896
6897 // gallery filter tags
6898 G.galleryFilterTags.lN = G.O.galleryFilterTags;
6899 G.galleryFilterTags.l1 = G.O.galleryL1FilterTags != null ? G.O.galleryL1FilterTags : G.O.galleryFilterTags;
6900
6901 // gallery filter tags mode
6902 G.galleryFilterTagsMode.lN = G.O.galleryFilterTagsMode;
6903 G.galleryFilterTagsMode.l1 = G.O.galleryL1FilterTagsMode != null ? G.O.galleryL1FilterTagsMode : G.O.galleryFilterTagsMode;
6904
6905 // gallery pagination
6906 G.O.galleryPaginationMode = G.O.galleryPaginationMode.toUpperCase();
6907
6908 if( toType(G.O.slideshowDelay) == 'number' && G.O.slideshowDelay >= 2000 ) {
6909 G.VOM.slideshowDelay = G.O.slideshowDelay;
6910 }
6911 else {
6912 NanoConsoleLog(G, 'Parameter "slideshowDelay" must be an integer >= 2000 ms.');
6913 }
6914
6915 // gallery display transition
6916 if( typeof G.O.thumbnailDisplayTransition == 'boolean' ) {
6917 if( G.O.thumbnailDisplayTransition === true ) {
6918 G.tn.opt.lN.displayTransition = 'FADEIN';
6919 G.tn.opt.l1.displayTransition = 'FADEIN';
6920 }
6921 else {
6922 G.tn.opt.lN.displayTransition = 'NONE';
6923 G.tn.opt.l1.displayTransition = 'NONE';
6924 }
6925 }
6926
6927 if( G.O.fnThumbnailDisplayEffect !== '' ) {
6928 G.tn.opt.lN.displayTransition = 'CUSTOM';
6929 G.tn.opt.l1.displayTransition = 'CUSTOM';
6930 }
6931 if( G.O.fnThumbnailL1DisplayEffect !== '' ) {
6932 G.tn.opt.l1.displayTransition = 'CUSTOM';
6933 }
6934
6935
6936 // thumbnail display transition easing
6937 // set default easing
6938 ThumbnailOpt('thumbnailDisplayTransitionEasing', 'thumbnailL1DisplayTransitionEasing', 'displayTransitionEasing');
6939 // parse thumbnail display transition
6940 function thumbnailDisplayTransitionParse( cfg, level ) {
6941 if( typeof cfg == 'string' ) {
6942 var st=cfg.split('_');
6943 if( st.length == 1 ) {
6944 G.tn.opt[level]['displayTransition'] = cfg.toUpperCase();
6945 }
6946 if( st.length == 2 ) {
6947 G.tn.opt[level]['displayTransition'] = st[0].toUpperCase();
6948 G.tn.opt[level]['displayTransitionStartVal'] = Number(st[1]);
6949 }
6950 if( st.length == 3 ) {
6951 G.tn.opt[level]['displayTransition'] = st[0].toUpperCase();
6952 G.tn.opt[level]['displayTransitionStartVal'] = Number(st[1]);
6953 G.tn.opt[level]['displayTransitionEasing'] = st[2];
6954 }
6955 }
6956 }
6957 thumbnailDisplayTransitionParse( G.O.thumbnailDisplayTransition, 'lN');
6958 thumbnailDisplayTransitionParse( G.O.thumbnailDisplayTransition, 'l1');
6959 thumbnailDisplayTransitionParse( G.O.thumbnailL1DisplayTransition, 'l1');
6960
6961
6962 // thumbnail display transition duration
6963 ThumbnailOpt('thumbnailDisplayTransitionDuration', 'thumbnailL1DisplayTransitionDuration', 'displayTransitionDuration');
6964 // thumbnail display transition interval duration
6965 ThumbnailOpt('thumbnailDisplayInterval', 'thumbnailL1DisplayInterval', 'displayInterval');
6966 // thumbnail display order
6967 ThumbnailOpt('thumbnailDisplayOrder', 'thumbnailL1DisplayOrder', 'displayOrder');
6968
6969
6970 // resolution breakpoints --> convert old syntax to new one
6971 if( G.O.thumbnailSizeSM !== undefined ) { G.O.breakpointSizeSM = G.O.thumbnailSizeSM; }
6972 if( G.O.thumbnailSizeME !== undefined ) { G.O.breakpointSizeME = G.O.thumbnailSizeME; }
6973 if( G.O.thumbnailSizeLA !== undefined ) { G.O.breakpointSizeLA = G.O.thumbnailSizeLA; }
6974 if( G.O.thumbnailSizeXL !== undefined ) { G.O.breakpointSizeXL = G.O.thumbnailSizeXL; }
6975
6976 // THUMBNAIL BUILD INIT
6977 //level 1
6978 if( G.O.thumbnailL1BuildInit2 !== undefined ) {
6979 var t1 = G.O.thumbnailL1BuildInit2.split('|');
6980 for( var i = 0; i < t1.length; i++ ) {
6981 var o1 = t1[i].trim().split('_');
6982 if( o1.length == 3 ) {
6983 var i1 = NewTBuildInit();
6984 i1.element = ThumbnailOverEffectsGetCSSElement(o1[0], '');
6985 i1.property = o1[1];
6986 i1.value = o1[2];
6987 G.tn.buildInit.level1.push(i1);
6988 }
6989 }
6990 }
6991 //level N
6992 if( G.O.thumbnailBuildInit2 !== undefined ) {
6993 var t1 = G.O.thumbnailBuildInit2.split('|');
6994 for( var i = 0; i < t1.length; i++ ) {
6995 var o1 = t1[i].trim().split('_');
6996 if( o1.length == 3 ) {
6997 var i1 = NewTBuildInit();
6998 i1.element = ThumbnailOverEffectsGetCSSElement(o1[0], '');
6999 i1.property = o1[1];
7000 i1.value = o1[2];
7001 G.tn.buildInit.std.push(i1);
7002 }
7003 }
7004 }
7005
7006
7007 // THUMBNAIL HOVER EFFETCS
7008
7009 // thumbnails hover effects - Level1
7010 var tL1HE = G.O.thumbnailL1HoverEffect2;
7011 if( tL1HE !== undefined ) {
7012 switch( toType(tL1HE) ) {
7013 case 'string': {
7014 let tmp = tL1HE.split('|');
7015 for(var i = 0; i < tmp.length; i++) {
7016 let oDef = NewTHoverEffect();
7017 oDef = ThumbnailHoverEffectExtract( tmp[i].trim(), oDef );
7018 if( oDef != null ) {
7019 G.tn.hoverEffects.level1.push(oDef);
7020 }
7021 }
7022 break;
7023 }
7024 case 'object': {
7025 let oDef = NewTHoverEffect();
7026 oDef = jQuery.extend(oDef,tL1HE);
7027 oDef = ThumbnailHoverEffectExtract( oDef.name, oDef );
7028 if( oDef != null ) {
7029 G.tn.hoverEffects.level1.push(oDef);
7030 }
7031 break;
7032 }
7033 case 'array': {
7034 for(var i = 0; i < tL1HE.length; i++) {
7035 let oDef = NewTHoverEffect();
7036 oDef = jQuery.extend(oDef,tL1HE[i]);
7037 oDef = ThumbnailHoverEffectExtract( oDef.name, oDef );
7038 if( oDef != null ) {
7039 G.tn.hoverEffects.level1.push(oDef);
7040 }
7041 }
7042 break;
7043 }
7044 case 'null':
7045 break;
7046 default:
7047 NanoAlert(G, 'incorrect parameter for "thumbnailL1HoverEffect2".');
7048 }
7049 }
7050 G.tn.hoverEffects.level1 = ThumbnailOverEffectsPreset(G.tn.hoverEffects.level1);
7051
7052 // thumbnails hover effects - other levels
7053 var tHE = G.O.thumbnailHoverEffect2;
7054 switch( toType(tHE) ) {
7055 case 'string': {
7056 let tmp = tHE.split('|');
7057 for(var i = 0; i < tmp.length; i++) {
7058 let oDef = NewTHoverEffect();
7059 oDef = ThumbnailHoverEffectExtract( tmp[i].trim(), oDef );
7060 if( oDef != null ) {
7061 G.tn.hoverEffects.std.push(oDef);
7062 }
7063 }
7064 break;
7065 }
7066 case 'object': {
7067 let oDef = NewTHoverEffect();
7068 oDef = jQuery.extend(oDef, tHE);
7069 oDef = ThumbnailHoverEffectExtract( oDef.name, oDef );
7070 if( oDef != null ) {
7071 G.tn.hoverEffects.std.push(oDef);
7072 }
7073 break;
7074 }
7075 case 'array': {
7076 for(var i = 0; i < tHE.length; i++) {
7077 let oDef = NewTHoverEffect();
7078 oDef = jQuery.extend(oDef,tHE[i]);
7079 oDef = ThumbnailHoverEffectExtract( oDef.name, oDef );
7080 if( oDef!= null ) {
7081 G.tn.hoverEffects.std.push(oDef);
7082 }
7083 }
7084 break;
7085 }
7086 case 'null':
7087 break;
7088 default:
7089 NanoAlert(G, 'incorrect parameter for "thumbnailHoverEffect2".');
7090 }
7091 G.tn.hoverEffects.std = ThumbnailOverEffectsPreset(G.tn.hoverEffects.std);
7092
7093
7094 if( G.O.touchAnimationL1 == undefined ) {
7095 G.O.touchAnimationL1 = G.O.touchAnimation;
7096 }
7097
7098 // disable thumbnail touch animation when no hover effect defined
7099 if( G.tn.hoverEffects.std.length == 0 ) {
7100 if( G.tn.hoverEffects.level1.length == 0 ) {
7101 G.O.touchAnimationL1 = false;
7102 }
7103 G.O.touchAnimation = false;
7104 }
7105
7106
7107 // thumbnail sizes
7108 if( G.O.thumbnailHeight == 0 || G.O.thumbnailHeight == '' ) { G.O.thumbnailHeight = 'auto'; }
7109 if( G.O.thumbnailWidth == 0 || G.O.thumbnailWidth == '' ) { G.O.thumbnailWidth = 'auto'; }
7110 if( G.O.thumbnailL1Height == 0 || G.O.thumbnailL1Height == '' ) { G.O.thumbnailL1Height = 'auto'; }
7111 if( G.O.thumbnailL1Width == 0 || G.O.thumbnailL1Width == '' ) { G.O.thumbnailL1Width = 'auto'; }
7112
7113 // RETRIEVE ALL THUMBNAIL SIZES
7114 // ThumbnailSizes( 'thumbnailWidth', false, 'width');
7115 // ThumbnailSizes( 'thumbnailL1Width', true, 'width');
7116 // ThumbnailSizes( 'thumbnailHeight', false, 'height');
7117 // ThumbnailSizes( 'thumbnailL1Height', true, 'height');
7118 ResponsiveOption('thumbnailWidth', 'width', 'lN');
7119 ResponsiveOption('thumbnailWidth', 'width', 'l1');
7120 ResponsiveOption('thumbnailL1Width', 'width', 'l1');
7121 ResponsiveOption('thumbnailHeight', 'height', 'lN');
7122 ResponsiveOption('thumbnailHeight', 'height', 'l1');
7123 ResponsiveOption('thumbnailL1Height', 'height', 'l1');
7124
7125
7126 G.O.thumbnailLabelHeight = parseInt(G.O.thumbnailLabelHeight);
7127
7128
7129 // retrieve all mosaic layout patterns
7130 // default pattern
7131 if( G.O.galleryMosaic != undefined ) {
7132 // clone object
7133 G.tn.settings.mosaic.l1.xs = JSON.parse(JSON.stringify(G.O.galleryMosaic));
7134 G.tn.settings.mosaic.l1.sm = JSON.parse(JSON.stringify(G.O.galleryMosaic));
7135 G.tn.settings.mosaic.l1.me = JSON.parse(JSON.stringify(G.O.galleryMosaic));
7136 G.tn.settings.mosaic.l1.la = JSON.parse(JSON.stringify(G.O.galleryMosaic));
7137 G.tn.settings.mosaic.l1.xl = JSON.parse(JSON.stringify(G.O.galleryMosaic));
7138 G.tn.settings.mosaic.lN.xs = JSON.parse(JSON.stringify(G.O.galleryMosaic));
7139 G.tn.settings.mosaic.lN.sm = JSON.parse(JSON.stringify(G.O.galleryMosaic));
7140 G.tn.settings.mosaic.lN.me = JSON.parse(JSON.stringify(G.O.galleryMosaic));
7141 G.tn.settings.mosaic.lN.la = JSON.parse(JSON.stringify(G.O.galleryMosaic));
7142 G.tn.settings.mosaic.lN.xl = JSON.parse(JSON.stringify(G.O.galleryMosaic));
7143 G.tn.settings.mosaicCalcFactor('l1', 'xs');
7144 G.tn.settings.mosaicCalcFactor('l1', 'sm');
7145 G.tn.settings.mosaicCalcFactor('l1', 'me');
7146 G.tn.settings.mosaicCalcFactor('l1', 'la');
7147 G.tn.settings.mosaicCalcFactor('l1', 'xl');
7148 G.tn.settings.mosaicCalcFactor('lN', 'xs');
7149 G.tn.settings.mosaicCalcFactor('lN', 'sm');
7150 G.tn.settings.mosaicCalcFactor('lN', 'me');
7151 G.tn.settings.mosaicCalcFactor('lN', 'la');
7152 G.tn.settings.mosaicCalcFactor('lN', 'xl');
7153 }
7154 if( G.O.galleryL1Mosaic != undefined ) {
7155 // default L1 pattern
7156 G.tn.settings.mosaic.l1.xs = JSON.parse(JSON.stringify(G.O.galleryL1Mosaic));
7157 G.tn.settings.mosaic.l1.sm = JSON.parse(JSON.stringify(G.O.galleryL1Mosaic));
7158 G.tn.settings.mosaic.l1.me = JSON.parse(JSON.stringify(G.O.galleryL1Mosaic));
7159 G.tn.settings.mosaic.l1.la = JSON.parse(JSON.stringify(G.O.galleryL1Mosaic));
7160 G.tn.settings.mosaic.l1.xl = JSON.parse(JSON.stringify(G.O.galleryL1Mosaic));
7161 G.tn.settings.mosaicCalcFactor('l1', 'xs');
7162 G.tn.settings.mosaicCalcFactor('l1', 'sm');
7163 G.tn.settings.mosaicCalcFactor('l1', 'me');
7164 G.tn.settings.mosaicCalcFactor('l1', 'la');
7165 G.tn.settings.mosaicCalcFactor('l1', 'xl');
7166 }
7167
7168 var lst=['xs','sm','me','la','xl'];
7169 // retrieve responsive mosaic definition for levels l1 & lN
7170 for( var w = 0; w < lst.length; w++ ) {
7171 if( G.O['galleryMosaic' + lst[w].toUpperCase()] != undefined ) {
7172 G.tn.settings.mosaic.lN[lst[w]] = JSON.parse(JSON.stringify( G.O['galleryMosaic' + lst[w].toUpperCase()] ));
7173 G.tn.settings.mosaic.l1[lst[w]] = JSON.parse(JSON.stringify( G.O['galleryMosaic' + lst[w].toUpperCase()] ));
7174 G.tn.settings.mosaicCalcFactor('lN',lst[w]);
7175 G.tn.settings.mosaicCalcFactor('l1', lst[w]);
7176 }
7177 }
7178 // retrieve responsive mosaic definition for level l1
7179 for( var w = 0; w < lst.length; w++ ) {
7180 if( G.O['galleryL1Mosaic' + lst[w].toUpperCase()] != undefined ) {
7181 G.tn.settings.mosaic.l1[lst[w]] = JSON.parse(JSON.stringify( G.O['galleryL1Mosaic' + lst[w].toUpperCase()] ));
7182 G.tn.settings.mosaicCalcFactor('l1', lst[w]);
7183 }
7184 }
7185
7186 G.O.imageTransition = G.O.imageTransition.toUpperCase();
7187
7188 G.layout.SetEngine();
7189
7190 // init plugins
7191 switch( G.O.kind ) {
7192 // MARKUP / API
7193 case '':
7194 break;
7195 // JSON, Flickr, Picasa, ...
7196 default:
7197 jQuery.nanogallery2['data_' + G.O.kind](G, 'Init' );
7198 }
7199
7200 }
7201
7202 // HOVER EFFECTS
7203 function ThumbnailHoverEffectExtract( name, effect) {
7204 var easings = [ 'easeInQuad', 'easeOutQuad', 'easeInOutQuad', 'easeInCubic', 'easeOutCubic', 'easeInOutCubic', 'easeInQuart', 'easeOutQuart', 'easeInOutQuart', 'easeInQuint', 'easeOutQuint', 'easeInOutQuint', 'easeInSine', 'easeOutSine', 'easeInOutSine', 'easeInExpo', 'easeOutExpo', 'easeInOutExpo', 'easeInCirc', 'easeOutCirc', 'easeInOutCirc', 'easeOutBounce', 'easeInBack', 'easeOutBack', 'easeInOutBack', 'elastic', 'bounce'];
7205
7206 var sp = name.split('_');
7207 if( sp.length >= 4 ) {
7208 // var oDef=NewTHoverEffect();
7209 effect.name = '';
7210 effect.type = sp[1];
7211 effect.from = sp[2];
7212 effect.to = sp[3];
7213 if( sp.length >= 5 ) {
7214 // effect.duration=sp[4];
7215
7216 for( var n = 4; n < sp.length; n++ ) {
7217 var v = sp[n];
7218
7219 // check if an easing name
7220 var foundEasing = false;
7221 for( var e = 0; e < easings.length; e++) {
7222 if( v == easings[e] ) {
7223 foundEasing = true;
7224 effect.easing = v;
7225 break;
7226 }
7227 }
7228 if( foundEasing === true ) {
7229 continue;
7230 }
7231
7232 v = v.toUpperCase();
7233
7234 if( v == 'HOVERIN' ) {
7235 effect.hoverout = false;
7236 continue;
7237 }
7238 if( v == 'HOVEROUT' ) {
7239 effect.hoverin = false;
7240 continue;
7241 }
7242
7243 if( v == 'KEYFRAME' ) {
7244 effect.firstKeyframe = false;
7245 continue;
7246 }
7247
7248 var num = parseInt(v.replace(/[^0-9\.]/g, ''), 10); // extract a number if one exists
7249
7250 if( num > 0 ) {
7251 // the string contains a numbers > 0
7252 if( v.indexOf('DURATION') >= 0 ) {
7253 effect.duration = num;
7254 continue;
7255 }
7256 if( v.indexOf('DURATIONBACK') >= 0 ) {
7257 effect.durationBack = num;
7258 continue;
7259 }
7260 if( v.indexOf('DELAY') >= 0 ) {
7261 effect.delay = num;
7262 continue;
7263 }
7264 if( v.indexOf('DELAYBACK') >= 0 ) {
7265 effect.delayBack = num;
7266 continue;
7267 }
7268
7269 // no parameter name found -> default is duration
7270 effect.duration = num;
7271 }
7272 }
7273 }
7274 effect.element = ThumbnailOverEffectsGetCSSElement(sp[0], effect.type);
7275
7276 }
7277 else {
7278 effect.name = name;
7279 // NanoAlert(G, 'incorrect parameter for "thumbnailHoverEffect": ' + name);
7280 // return null;
7281 }
7282 return effect;
7283 }
7284
7285
7286 function ThumbnailOverEffectsGetCSSElement( element, property ) {
7287
7288 var elts = {
7289 'image': '.nGY2GThumbnailImage',
7290 'thumbnail': '.nGY2GThumbnail',
7291 'label': '.nGY2GThumbnailLabel',
7292 'title': '.nGY2GThumbnailTitle',
7293 'description': '.nGY2GThumbnailDescription',
7294 'tools': '.nGY2GThumbnailIcons',
7295 'customlayer': '.nGY2GThumbnailCustomLayer',
7296 'default': 'nGY2GThumbnailImage'
7297 };
7298 return (elts[element] || elts['default']);
7299
7300
7301 }
7302
7303 // convert preset hover effects (nanoGALLERY) to new ones (nanogallery2)
7304 function ThumbnailOverEffectsPreset( effects ) {
7305
7306 // COMPATIBILITY WITH nanoGALLERY
7307 // OK:
7308 // 'borderLighter', 'borderDarker', 'scale120', 'labelAppear', 'labelAppear75', 'labelOpacity50', 'scaleLabelOverImage'
7309 // 'overScale', 'overScaleOutside', 'descriptionAppear'
7310 // 'slideUp', 'slideDown', 'slideRight', 'slideLeft'
7311 // 'imageScale150', 'imageScaleIn80', 'imageScale150Outside', 'imageSlideUp', 'imageSlideDown', 'imageSlideRight', 'imageSlideLeft'
7312 // 'labelSlideUpTop', 'labelSlideUp', 'labelSlideDown', 'descriptionSlideUp'
7313 // KO:
7314 // 'labelSplit4', 'labelSplitVert', 'labelAppearSplit4', 'labelAppearSplitVert'
7315 // TODO:
7316 // 'rotateCornerBL', 'rotateCornerBR', 'imageSplit4', 'imageSplitVert', 'imageRotateCornerBL', 'imageRotateCornerBR', 'imageFlipHorizontal', 'imageFlipVertical'
7317
7318
7319
7320 var newEffects=[];
7321 for( var i=0; i< effects.length; i++ ) {
7322 switch( effects[i].name.toUpperCase() ) {
7323 case 'BORDERLIGHTER': {
7324 let rgb = ColorHelperToRGB(GalleryThemeGetCurrent().thumbnail.borderColor);
7325 let name = 'thumbnail_borderColor_'+rgb+'_'+ShadeBlendConvert(0.5, rgb );
7326 newEffects.push(ThumbnailHoverEffectExtract(name, effects[i]));
7327 break;
7328 }
7329 case 'BORDERDARKER': {
7330 let rgb = ColorHelperToRGB(GalleryThemeGetCurrent().thumbnail.borderColor);
7331 let name = 'thumbnail_borderColor_'+rgb+'_'+ShadeBlendConvert(-0.5, rgb );
7332 newEffects.push(ThumbnailHoverEffectExtract(name, effects[i]));
7333 break;
7334 }
7335 case 'SCALE120':
7336 newEffects.push(ThumbnailHoverEffectExtract('thumbnail_scale_1.00_1.20', effects[i]));
7337 break;
7338 case 'LABELAPPEAR':
7339 case 'LABELAPPEAR75':
7340 newEffects.push(ThumbnailHoverEffectExtract('label_opacity_0.00_1.00', effects[i]));
7341 break;
7342 case 'TOOLSAPPEAR':
7343 newEffects.push(ThumbnailHoverEffectExtract('tools_opacity_0_1', effects[i]));
7344 break;
7345 case 'TOOLSSLIDEDOWN':
7346 newEffects.push(ThumbnailHoverEffectExtract('tools_translateY_-100%_0%', effects[i]));
7347 break;
7348 case 'TOOLSSLIDEUP':
7349 newEffects.push(ThumbnailHoverEffectExtract('tools_translateY_100%_0%', effects[i]));
7350 break;
7351 case 'LABELOPACITY50':
7352 newEffects.push(ThumbnailHoverEffectExtract('label_opacity_1.00_0.50', effects[i]));
7353 break;
7354 case 'LABELSLIDEUPTOP':
7355 case 'LABELSLIDEUP':
7356 newEffects.push(ThumbnailHoverEffectExtract('label_translateY_100%_0%', effects[i]));
7357 newEffects.push(ThumbnailHoverEffectExtract('label_translateY_100%_0%', effects[i]));
7358 break;
7359 case 'LABELSLIDEDOWN':
7360 newEffects.push(ThumbnailHoverEffectExtract('label_translateY_-100%_0%', effects[i]));
7361 break;
7362 case 'SCALELABELOVERIMAGE':
7363 newEffects.push(ThumbnailHoverEffectExtract('label_scale_0.00_1.00', effects[i]));
7364 var n = cloneJSObject(effects[i]);
7365 newEffects.push(ThumbnailHoverEffectExtract('image_scale_1.00_0.00', n));
7366 break;
7367 case 'OVERSCALE':
7368 case 'OVERSCALEOUTSIDE':
7369 //var name = 'label_scale_0_100';
7370 newEffects.push(ThumbnailHoverEffectExtract('label_scale_2.00_1.00', effects[i]));
7371 var n = cloneJSObject(effects[i]);
7372 newEffects.push(ThumbnailHoverEffectExtract('label_opacity_0.00_1.00', n));
7373 n = cloneJSObject(effects[i]);
7374 newEffects.push(ThumbnailHoverEffectExtract('image_scale_1.00_0.00', n));
7375 n = cloneJSObject(effects[i]);
7376 newEffects.push(ThumbnailHoverEffectExtract('image_opacity_1.00_0.00', n));
7377 break;
7378 case 'DESCRIPTIONAPPEAR':
7379 newEffects.push(ThumbnailHoverEffectExtract('description_opacity_0_1', effects[i]));
7380 break;
7381 case 'SLIDERIGHT':
7382 newEffects.push(ThumbnailHoverEffectExtract('image_translateX_0%_100%', effects[i]));
7383 newEffects.push(ThumbnailHoverEffectExtract('label_translateX_-100%_0%', cloneJSObject(effects[i])));
7384 break;
7385 case 'SLIDELEFT':
7386 newEffects.push(ThumbnailHoverEffectExtract('image_translateX_0%_-100%', effects[i]));
7387 newEffects.push(ThumbnailHoverEffectExtract('label_translateX_100%_0%', cloneJSObject(effects[i])));
7388 break;
7389 case 'SLIDEUP':
7390 newEffects.push(ThumbnailHoverEffectExtract('image_translateY_0%_-100%', effects[i]));
7391 newEffects.push(ThumbnailHoverEffectExtract('label_translateY_100%_0%', cloneJSObject(effects[i])));
7392 break;
7393 case 'SLIDEDOWN':
7394 newEffects.push(ThumbnailHoverEffectExtract('image_translateY_0%_100%', effects[i]));
7395 newEffects.push(ThumbnailHoverEffectExtract('label_translateY_-100%_0%', cloneJSObject(effects[i])));
7396 break;
7397 case 'IMAGESCALE150':
7398 case 'IMAGESCALE150OUTSIDE':
7399 newEffects.push(ThumbnailHoverEffectExtract('image_scale_1.00_1.50', effects[i]));
7400 break;
7401 case 'IMAGESCALEIN80':
7402 newEffects.push(ThumbnailHoverEffectExtract('image_scale_1.20_1.00', effects[i]));
7403 break;
7404 case 'IMAGESLIDERIGHT':
7405 newEffects.push(ThumbnailHoverEffectExtract('image_translateX_0%_100%', effects[i]));
7406 break;
7407 case 'IMAGESLIDELEFT':
7408 newEffects.push(ThumbnailHoverEffectExtract('image_translateX_0%_-100%', effects[i]));
7409 break;
7410 case 'IMAGESLIDEUP':
7411 newEffects.push(ThumbnailHoverEffectExtract('image_translateY_0%_-100%', effects[i]));
7412 break;
7413 case 'IMAGESLIDEDOWN':
7414 newEffects.push(ThumbnailHoverEffectExtract('image_translateY_0%_100%', effects[i]));
7415 break;
7416 case 'LABELSLIDEUPDOWN':
7417 newEffects.push(ThumbnailHoverEffectExtract('label_translateY_0%_100%', effects[i]));
7418 break;
7419 case 'DESCRIPTIONSLIDEUP':
7420 newEffects.push(ThumbnailHoverEffectExtract('description_translateY_110%_0%', effects[i]));
7421 break;
7422
7423 case 'IMAGEBLURON':
7424 newEffects.push(ThumbnailHoverEffectExtract('image_blur_2.00px_0.00px', effects[i]));
7425 break;
7426 case 'IMAGEBLUROFF':
7427 newEffects.push(ThumbnailHoverEffectExtract('image_blur_0.00px_2.00px', effects[i]));
7428 break;
7429 case 'IMAGEGRAYON':
7430 newEffects.push(ThumbnailHoverEffectExtract('image_grayscale_0%_100%', effects[i]));
7431 break;
7432 case 'IMAGEGRAYOFF':
7433 newEffects.push(ThumbnailHoverEffectExtract('image_grayscale_100%_0%', effects[i]));
7434 break;
7435 case 'IMAGESEPIAON':
7436 newEffects.push(ThumbnailHoverEffectExtract('image_sepia_100%_1%', effects[i]));
7437 break;
7438 case 'IMAGESEPIAOFF':
7439 newEffects.push(ThumbnailHoverEffectExtract('image_sepia_1%_100%', effects[i]));
7440 break;
7441
7442 default:
7443 newEffects.push(effects[i]);
7444 break;
7445 }
7446 }
7447
7448 return newEffects;
7449 }
7450
7451
7452 // Thumbnail hover effect definition
7453 function NewTHoverEffect() {
7454 var oDef={
7455 name: '',
7456 element: '', // element class
7457 type: '',
7458 from: '', // start value
7459 to: '', // end value
7460 hoverin: true,
7461 hoverout: true,
7462 firstKeyframe: true,
7463 delay: 0,
7464 delayBack: 0,
7465 duration: 400,
7466 durationBack: 300,
7467 easing: 'easeOutQuart',
7468 easingBack: 'easeOutQuart',
7469 animParam: null
7470 };
7471 return oDef;
7472 }
7473
7474 function NewTBuildInit() {
7475 // to set CSS properties
7476 var oDef={ element: '', property: '', value: '' };
7477 return oDef;
7478 }
7479
7480
7481 function ThumbnailStyle( cfg, level) {
7482
7483 switch( cfg.position ){
7484 case 'onBottom' :
7485 G.tn.style[level]['label'] = 'bottom:0; ';
7486 break;
7487 case 'right' :
7488 switch( cfg.valign ) {
7489 case 'top':
7490 G.tn.style[level]['label'] = 'top:0; position:absolute; left: 50%;';
7491 break;
7492 case 'middle':
7493 G.tn.style[level]['label'] = 'top:0; bottom:0; left: 50%;';
7494 G.tn.style[level]['title'] = 'position:absolute; bottom:50%;';
7495 G.tn.style[level]['desc'] = 'position:absolute; top:50%;';
7496 break;
7497 case 'bottom':
7498 default:
7499 G.tn.style[level].label = 'bottom:0; position:absolute; left: 50%;';
7500 G.tn.style[level].title = 'position:absolute;bottom:0;';
7501 break;
7502 }
7503 break;
7504 case 'custom':
7505 break;
7506 default:
7507 case 'overImage' :
7508 switch( cfg.valign ) {
7509 case 'top':
7510 G.tn.style[level]['label'] = 'top:0; position:absolute;';
7511 break;
7512 case 'middle':
7513 G.tn.style[level]['label'] = 'top:0; bottom:0;';
7514 G.tn.style[level]['title'] = 'position:absolute; bottom:50%;';
7515 G.tn.style[level]['desc'] = 'position:absolute; top:50%;';
7516 break;
7517 case 'bottom':
7518 default:
7519 // G.O.thumbnailLabel.position = 'overImageOnBottom';
7520 G.tn.style[level].label = 'bottom:0; position:absolute;';
7521 break;
7522 }
7523
7524 // case 'overImageOnTop' :
7525 // G.tn.style[level]['label'] = 'top:0; position:absolute;';
7526 // break;
7527 // case 'overImageOnMiddle' :
7528 // G.tn.style[level]['label'] = 'top:0; bottom:0;';
7529 // G.tn.style[level]['title'] = 'position:absolute; bottom:50%;';
7530 // G.tn.style[level]['desc'] = 'position:absolute; top:50%;';
7531 // break;
7532 // case 'right' :
7533 // case 'custom' :
7534 // break;
7535 // case 'overImageOnBottom' :
7536 // default :
7537 // G.O.thumbnailLabel.position = 'overImageOnBottom';
7538 // G.tn.style[level].label = 'bottom:0; position:absolute;';
7539 // break;
7540 }
7541
7542 // if( G.layout.engine != 'CASCADING' ) {
7543 if( cfg.position != 'onBottom' ) {
7544 // multi-line
7545 if( cfg.titleMultiLine ) {
7546 G.tn.style[level]['title'] += 'white-space:normal;';
7547 }
7548 if( cfg.descriptionMultiLine ) {
7549 G.tn.style[level]['desc'] += 'white-space:normal;';
7550 }
7551 }
7552
7553 // horizontal alignement
7554 switch( cfg.align ) {
7555 case 'right':
7556 G.tn.style[level].label += 'text-align:right;';
7557 break;
7558 case 'left':
7559 G.tn.style[level].label += 'text-align:left;';
7560 break;
7561 default:
7562 G.tn.style[level].label += 'text-align:center;';
7563 break;
7564 }
7565
7566
7567 if( cfg.titleFontSize != undefined && cfg.titleFontSize != '' ) {
7568 G.tn.style[level].title += 'font-size:' + cfg.titleFontSize + ';';
7569 }
7570 if( cfg.descriptionFontSize != undefined && cfg.descriptionFontSize != '' ) {
7571 G.tn.style[level].desc += 'font-size:' + cfg.descriptionFontSize + ';';
7572 }
7573
7574 if( cfg.displayDescription == false ) {
7575 G.tn.style[level].desc += 'display:none;';
7576 }
7577 }
7578
7579
7580 // cache some thumbnail settings
7581 function ThumbnailDefCaches() {
7582 // thumbnail content CSS styles
7583
7584 // settings for level L1 and LN
7585 ThumbnailStyle( G.O.thumbnailLabel, 'lN');
7586 if( G.O.thumbnailL1Label !== undefined ) {
7587 ThumbnailStyle( G.O.thumbnailL1Label, 'l1');
7588 }
7589 else {
7590 ThumbnailStyle( G.O.thumbnailLabel, 'l1');
7591 }
7592
7593 if( G.O.thumbnailL1Label && G.O.thumbnailL1Label.display ) {
7594 // settings for level L1
7595 ThumbnailStyle( G.O.thumbnailL1Label, 'l1');
7596 }
7597
7598
7599 // default thumbnail sizes levels l1 and lN
7600 var lst=['xs','sm','me','la','xl'];
7601 for( var i = 0; i < lst.length; i++ ) {
7602 var w = G.tn.settings.width.lN[lst[i]];
7603 if( w != 'auto' ) {
7604 G.tn.defaultSize.width.lN[lst[i]] = w;
7605 G.tn.defaultSize.width.l1[lst[i]] = w;
7606 }
7607 else {
7608 var h = G.tn.settings.height.lN[lst[i]];
7609 G.tn.defaultSize.width.lN[lst[i]] = h; // dynamic width --> set height value as default for the width
7610 G.tn.defaultSize.width.l1[lst[i]] = h; // dynamic width --> set height value as default
7611 }
7612 }
7613 for( var i = 0; i < lst.length; i++ ) {
7614 var h = G.tn.settings.height.lN[lst[i]];
7615 if( h != 'auto' ) {
7616 // grid or justified layout
7617 G.tn.defaultSize.height.lN[lst[i]] = h; //+G.tn.labelHeight.get();
7618 G.tn.defaultSize.height.l1[lst[i]] = h; //+G.tn.labelHeight.get();
7619 }
7620 else {
7621 var w = G.tn.settings.width.lN[lst[i]];
7622 G.tn.defaultSize.height.lN[lst[i]] = w; // dynamic height --> set width value as default for the height
7623 G.tn.defaultSize.height.l1[lst[i]] = w; // dynamic height --> set width value as default
7624 }
7625 }
7626
7627 // default thumbnail sizes levels l1
7628 for( var i = 0; i < lst.length; i++ ) {
7629 var w = G.tn.settings.width.l1[lst[i]];
7630 if( w != 'auto' ) {
7631 G.tn.defaultSize.width.l1[lst[i]] = w;
7632 }
7633 else {
7634 var h = G.tn.settings.height.l1[lst[i]];
7635 G.tn.defaultSize.width.l1[lst[i]] = h; // dynamic width --> set height value as default
7636 }
7637 }
7638 for( var i = 0; i < lst.length; i++ ) {
7639 var h = G.tn.settings.height.l1[lst[i]];
7640 if( h != 'auto' ) {
7641 // grid or justified layout
7642 G.tn.defaultSize.height.l1[lst[i]] = h; //+G.tn.labelHeight.get();
7643 }
7644 else {
7645 var w = G.tn.settings.width.l1[lst[i]];
7646 G.tn.defaultSize.height.l1[lst[i]]= w ; // dynamic height --> set width value as default
7647 }
7648 }
7649
7650 }
7651
7652
7653 //
7654 function GalleryThemeGetCurrent() {
7655
7656 var cs=null;
7657 switch(toType(G.O.galleryTheme)) {
7658 case 'object': // user custom color scheme object
7659 cs = G.galleryTheme_dark; // default color scheme
7660 jQuery.extend(true,cs,G.O.galleryTheme);
7661 break;
7662 case 'string': // name of an internal defined color scheme
7663 switch( G.O.galleryTheme ) {
7664 case 'light':
7665 cs = G.galleryTheme_light;
7666 break;
7667 case 'default':
7668 case 'dark':
7669 case 'none':
7670 default:
7671 cs = G.galleryTheme_dark;
7672 }
7673 break;
7674 default:
7675 cs = G.galleryTheme_dark;
7676 }
7677 return cs;
7678 }
7679
7680 // ##### BREADCRUMB/THUMBNAIL COLOR SCHEME #####
7681 function SetGalleryTheme() {
7682
7683 if( typeof G.O.colorScheme !== 'undefined' ) {
7684 G.O.galleryTheme = G.O.colorScheme;
7685 }
7686
7687 var cs = null;
7688 var galleryTheme = '';
7689 switch(toType(G.O.galleryTheme)) {
7690 case 'object': // user custom color scheme object
7691 cs = G.galleryTheme_dark; // default color scheme
7692 jQuery.extend(true,cs,G.O.galleryTheme);
7693 galleryTheme='nanogallery_gallerytheme_custom_' + G.baseEltID;
7694 break;
7695 case 'string': // name of an internal defined color scheme
7696 switch( G.O.galleryTheme ) {
7697 case 'light':
7698 cs = G.galleryTheme_light;
7699 galleryTheme='nanogallery_gallerytheme_light_' + G.baseEltID;
7700 break;
7701 case 'default':
7702 case 'dark':
7703 case 'none':
7704 default:
7705 cs = G.galleryTheme_dark;
7706 galleryTheme='nanogallery_gallerytheme_dark_' + G.baseEltID;
7707 }
7708 break;
7709 default:
7710 NanoAlert(G, 'Error in galleryTheme parameter.');
7711 return;
7712 }
7713
7714 //var s1='.nanogallery_theme_'+G.O.theme+' ';
7715 var s1='.' + galleryTheme + ' ';
7716
7717 // navigation bar
7718 var c = cs.navigationBar;
7719 var s=s1+'.nGY2Navigationbar { background:'+c.background+'; }'+'\n';
7720 if( c.border !== undefined && c.border !== '' ) { s+=s1+'.nGY2Navigationbar { border:'+c.border+'; }'+'\n'; }
7721 if( c.borderTop !== undefined && c.borderTop !== '' ) { s+=s1+'.nGY2Navigationbar { border-top:'+c.borderTop+'; }'+'\n'; }
7722 if( c.borderBottom !== undefined && c.borderBottom !== '' ) { s+=s1+'.nGY2Navigationbar { border-bottom:'+c.borderBottom+'; }'+'\n'; }
7723 if( c.borderRight !== undefined && c.borderRight !== '' ) { s+=s1+'.nGY2Navigationbar { border-right:'+c.borderRight+'; }'+'\n'; }
7724 if( c.borderLeft !== undefined && c.borderLeft !== '' ) { s+=s1+'.nGY2Navigationbar { border-left:'+c.borderLeft+'; }'+'\n'; }
7725
7726 // navigation bar - breadcrumb
7727 var c = cs.navigationBreadcrumb;
7728 s+=s1+'.nGY2Breadcrumb { background:'+c.background+'; border-radius:'+c.borderRadius+'; }'+'\n';
7729 s+=s1+'.nGY2Breadcrumb .oneItem { color:'+c.color+'; }'+'\n';
7730 s+=s1+'.nGY2Breadcrumb .oneItem:hover { color:'+c.colorHover+'; }'+'\n';
7731
7732 // navigation bar - tag filter
7733 var c = cs.navigationFilter;
7734 s+=s1+'.nGY2NavFilterUnselected { color:'+c.color+'; background:'+c.background+'; border-radius:'+c.borderRadius+'; }'+'\n';
7735 s+=s1+'.nGY2NavFilterSelected { color:'+c.colorSelected+'; background:'+c.backgroundSelected+'; border-radius:'+c.borderRadius+'; }'+'\n';
7736 s+=s1+'.nGY2NavFilterSelectAll { color:'+c.colorSelected+'; background:'+c.background+'; border-radius:'+c.borderRadius+'; }'+'\n';
7737
7738 // navigation bar - pagination next/previous
7739 var c = cs.navigationPagination;
7740 s+=s1+'.nGY2NavPagination { color:'+c.color+'; background:'+c.background+'; border-radius:'+c.borderRadius+'; }'+'\n';
7741 s+=s1+'.nGY2NavPagination:hover { color:'+c.colorHover+'; }'+'\n';
7742
7743 // thumbnails
7744 var c = cs.thumbnail;
7745 // s+=s1+'.nGY2GThumbnail { border-radius: '+c.borderRadius+'; background:'+c.background+'; border-color:'+c.borderColor+'; border-top-width:'+G.tn.opt.Get('borderVertical')+'px; border-right-width:'+G.tn.opt.Get('borderHorizontal')+'px; border-bottom-width:'+G.tn.opt.Get('borderVertical')+'px; border-left-width:'+G.tn.opt.Get('borderHorizontal')+'px;}'+'\n';
7746 s+=s1+'.nGY2GThumbnail { border-radius: '+c.borderRadius+'; background:'+c.background+'; border-color:'+c.borderColor+'; }'+'\n';
7747 s+=s1+'.nGY2GThumbnail_l1 { border-top-width:'+G.tn.opt.l1.borderVertical+'px; border-right-width:'+G.tn.opt.l1.borderHorizontal+'px; border-bottom-width:'+G.tn.opt.l1.borderVertical+'px; border-left-width:'+G.tn.opt.l1.borderHorizontal+'px;}'+'\n';
7748 s+=s1+'.nGY2GThumbnail_lN { border-top-width:'+G.tn.opt.lN.borderVertical+'px; border-right-width:'+G.tn.opt.lN.borderHorizontal+'px; border-bottom-width:'+G.tn.opt.lN.borderVertical+'px; border-left-width:'+G.tn.opt.lN.borderHorizontal+'px;}'+'\n';
7749 s+=s1+'.nGY2GThumbnailStack { background:'+c.stackBackground+'; }'+'\n';
7750 // s+=s1+'.nGY2GThumbnailImage { background:'+cs.thumbnail.background+'; background-image:'+cs.thumbnail.backgroundImage+'; }'+'\n';
7751 s+=s1+'.nGY2TnImgBack { background:'+c.background+'; background-image:'+c.backgroundImage+'; }'+'\n';
7752 s+=s1+'.nGY2GThumbnailAlbumUp { background:'+c.background+'; background-image:'+c.backgroundImage+'; color:'+cs.thumbnail.titleColor+'; }'+'\n';
7753 s+=s1+'.nGY2GThumbnailIconsFullThumbnail { color:'+c.titleColor+'; }\n';
7754 s+=s1+'.nGY2GThumbnailLabel { background:'+c.labelBackground+'; opacity:'+c.labelOpacity+'; }'+'\n';
7755 s+=s1+'.nGY2GThumbnailImageTitle { color:'+c.titleColor+'; background-color:'+c.titleBgColor+'; '+(c.titleShadow =='' ? '': 'Text-Shadow:'+c.titleShadow+';')+' }'+'\n';
7756 s+=s1+'.nGY2GThumbnailAlbumTitle { color:'+c.titleColor+'; background-color:'+c.titleBgColor+'; '+(c.titleShadow =='' ? '': 'Text-Shadow:'+c.titleShadow+';')+' }'+'\n';
7757 s+=s1+'.nGY2GThumbnailDescription { color:'+c.descriptionColor+'; background-color:'+c.descriptionBgColor+'; '+(c.descriptionShadow =='' ? '': 'Text-Shadow:'+c.descriptionShadow+';')+' }'+'\n';
7758
7759 // thumbnails - icons
7760 var c = cs.thumbnailIcon;
7761 s+=s1+'.nGY2GThumbnailIcons { padding:'+c.padding+'; }\n';
7762 s+=s1+'.nGY2GThumbnailIcon { color:'+c.color+'; '+(c.shadow =='' ? '': 'Text-Shadow:'+c.shadow+';')+' }\n';
7763 s+=s1+'.nGY2GThumbnailIconTextBadge { background-color:'+c.color+'; }\n';
7764
7765 // gallery pagination -> dot/rectangle based
7766 var c = cs.pagination;
7767 if( G.O.galleryPaginationMode != 'NUMBERS' ) {
7768 s+=s1+'.nGY2paginationDot { border:'+c.shapeBorder+'; background:'+c.shapeColor+';}\n';
7769 s+=s1+'.nGY2paginationDotCurrentPage { border:'+c.shapeBorder+'; background:'+c.shapeSelectedColor+';}\n';
7770 s+=s1+'.nGY2paginationRectangle { border:'+c.shapeBorder+'; background:'+c.shapeColor+';}\n';
7771 s+=s1+'.nGY2paginationRectangleCurrentPage { border:'+c.shapeBorder+'; background:'+c.shapeSelectedColor+';}\n';
7772 } else {
7773 s+=s1+'.nGY2paginationItem { background:'+c.background+'; color:'+c.color+'; border-radius:'+c.borderRadius+'; }\n';
7774 s+=s1+'.nGY2paginationItemCurrentPage { background:'+c.background+'; color:'+c.color+'; border-radius:'+c.borderRadius+'; }\n';
7775 s+=s1+'.nGY2PaginationPrev { background:'+c.background+'; color:'+c.color+'; border-radius:'+c.borderRadius+'; }\n';
7776 s+=s1+'.nGY2PaginationNext { background:'+c.background+'; color:'+c.color+'; border-radius:'+c.borderRadius+'; }\n';
7777 s+=s1+'.nGY2paginationItemCurrentPage { background:'+c.backgroundSelected+'; }\n';
7778 }
7779
7780 // gallery more button
7781 var c = cs.thumbnail;
7782 // s+=s1+'.nGY2GalleryMoreButtonAnnotation { background:'+c.background+'; border-color:'+c.borderColor+'; border-top-width:'+G.O.thumbnailBorderVertical+'px; border-right-width:'+G.O.thumbnailBorderHorizontal+'px; border-bottom-width:'+G.O.thumbnailBorderVertical+'px; border-left-width:'+G.O.thumbnailBorderHorizontal+'px;}\n';
7783 s+=s1+'.nGY2GalleryMoreButtonAnnotation { background:'+c.background+'; border-color:'+c.borderColor+'; border-top-width: 1px; border-right-width: 1px; border-bottom-width: 1px; border-left-width: 1px;}\n';
7784 s+=s1+'.nGY2GalleryMoreButtonAnnotation { color:'+c.titleColor+'; '+(c.titleShadow =='' ? '': 'Text-Shadow:'+c.titleShadow)+'; }\n';
7785
7786 jQuery('head').append('<style id="ngycs_'+G.baseEltID+'">'+s+'</style>');
7787 G.$E.base.addClass(galleryTheme);
7788
7789 };
7790
7791 // ##### VIEWER COLOR SCHEME #####
7792 function SetViewerTheme( ) {
7793
7794 if( G.VOM.viewerTheme != '' ) {
7795 G.VOM.$baseCont.addClass(G.VOM.viewerTheme);
7796 return;
7797 }
7798
7799 if( typeof G.O.colorSchemeViewer !== 'undefined' ) {
7800 G.O.viewerTheme = G.O.colorSchemeViewer;
7801 }
7802
7803 var cs=null;
7804 switch(toType(G.O.viewerTheme)) {
7805 case 'object': // user custom color scheme object
7806 cs = G.viewerTheme_dark;
7807 jQuery.extend(true, cs, G.O.viewerTheme);
7808 G.VOM.viewerTheme = 'nanogallery_viewertheme_custom_' + G.baseEltID;
7809 break;
7810 case 'string': // name of an internal defined color scheme
7811 switch( G.O.viewerTheme ) {
7812 case 'none':
7813 return;
7814 break;
7815 case 'light':
7816 cs = G.viewerTheme_light;
7817 G.VOM.viewerTheme = 'nanogallery_viewertheme_light_' + G.baseEltID;
7818 break;
7819 case 'dark':
7820 case 'default':
7821 cs = G.viewerTheme_dark;
7822 G.VOM.viewerTheme = 'nanogallery_viewertheme_dark_' + G.baseEltID;
7823 break;
7824 }
7825 break;
7826 default:
7827 NanoAlert(G, 'Error in viewerTheme parameter.');
7828 return;
7829 }
7830
7831 var s1 = '.' + G.VOM.viewerTheme + ' ';
7832 var s = s1 + '.nGY2Viewer { background:' + cs.background + '; }'+'\n';
7833 s += s1 + '.nGY2Viewer .toolbarBackground { background:' + cs.barBackground + '; }'+'\n';
7834 s += s1 + '.nGY2Viewer .toolbar { border:' + cs.barBorder + '; color:' + cs.barColor + '; }'+'\n';
7835 s += s1 + '.nGY2Viewer .toolbar .previousButton:after { color:' + cs.barColor + '; }'+'\n';
7836 s += s1 + '.nGY2Viewer .toolbar .nextButton:after { color:' + cs.barColor + '; }'+'\n';
7837 s += s1 + '.nGY2Viewer .toolbar .closeButton:after { color:' + cs.barColor + '; }'+'\n';
7838 s += s1 + '.nGY2Viewer .toolbar .label .title { color:' + cs.barColor + '; }'+'\n';
7839 s += s1 + '.nGY2Viewer .toolbar .label .description { color:' + cs.barDescriptionColor + '; }'+'\n';
7840 jQuery('head').append('<style>' + s + '</style>');
7841 G.VOM.$baseCont.addClass(G.VOM.viewerTheme);
7842 };
7843
7844
7845
7846 /** @function SetPolyFills */
7847 function SetPolyFills() {
7848
7849 // POLYFILL FOR BIND function --> for older Safari mobile
7850 // found on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind#Compatibility
7851 if (!Function.prototype.bind) {
7852 Function.prototype.bind = function (oThis) {
7853 if (typeof this !== "function") {
7854 // closest thing possible to the ECMAScript 5
7855 // internal IsCallable function
7856 throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
7857 }
7858
7859 var aArgs = Array.prototype.slice.call(arguments, 1),
7860 fToBind = this,
7861 fNOP = function () {},
7862 fBound = function () {
7863 return fToBind.apply(this instanceof fNOP && oThis
7864 ? this
7865 : oThis,
7866 aArgs.concat(Array.prototype.slice.call(arguments)));
7867 };
7868
7869 fNOP.prototype = this.prototype;
7870 fBound.prototype = new fNOP();
7871
7872 return fBound;
7873 };
7874 }
7875
7876 // requestAnimationFrame polyfill by Erik M�ller. fixes from Paul Irish and Tino Zijdel
7877 // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
7878 // http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
7879 // MIT license
7880 (function() {
7881 var lastTime = 0;
7882 var vendors = ['ms', 'moz', 'webkit', 'o'];
7883 for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
7884 window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
7885 window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
7886 }
7887 if (!window.requestAnimationFrame)
7888 window.requestAnimationFrame = function(callback, element) {
7889 var currTime = new Date().getTime();
7890 var timeToCall = Math.max(0, 16 - (currTime - lastTime));
7891 var id = window.setTimeout(function() { callback(currTime + timeToCall); }, timeToCall);
7892 lastTime = currTime + timeToCall;
7893 return id;
7894 };
7895
7896 if (!window.cancelAnimationFrame)
7897 window.cancelAnimationFrame = function(id) {
7898 clearTimeout(id);
7899 };
7900 }());
7901
7902 // array.removeIf -> removes items from array base on a function's result
7903 Array.prototype.removeIf = function(callback) {
7904 var i = this.length;
7905 while (i--) {
7906 if (callback(this[i], i)) {
7907 this.splice(i, 1);
7908 }
7909 }
7910 };
7911
7912 // IE11 for startsWith
7913 // thanks to @lichtamberg - https://github.com/lichtamberg
7914 if (!String.prototype.startsWith) {
7915 String.prototype.startsWith = function(searchString, position) {
7916 position = position || 0;
7917 return this.indexOf(searchString, position) === position;
7918 };
7919 }
7920
7921 }
7922
7923
7924 // Gallery clicked or toolbar touched -> retrieve & execute action
7925 function GalleryClicked(e) {
7926
7927 var r = GalleryEventRetrieveElementl(e, false);
7928
7929 if( r.GOMidx == -1 ) { return 'exit'; }
7930
7931 var idx = G.GOM.items[r.GOMidx].thumbnailIdx;
7932 if( G.GOM.slider.hostIdx == r.GOMidx ) {
7933 idx = G.GOM.items[G.GOM.slider.currentIdx].thumbnailIdx;
7934 }
7935 switch( r.action ) {
7936 case 'OPEN':
7937 ThumbnailOpen(idx, false);
7938 return 'exit';
7939 break;
7940 case 'DISPLAY':
7941 // used the display icon (ignore if selection mode)
7942 ThumbnailOpen(idx, true);
7943 return 'exit';
7944 break;
7945 case 'TOGGLESELECT':
7946 ThumbnailSelectionToggle(idx);
7947 return 'exit';
7948 break;
7949 case 'SHARE':
7950 PopupShare(idx);
7951 return 'exit';
7952 break;
7953 case 'DOWNLOAD':
7954 DownloadImage(idx);
7955 return 'exit';
7956 break;
7957 case 'INFO':
7958 ItemDisplayInfo(G.I[idx]);
7959 return 'exit';
7960 break;
7961 case 'SHOPPINGCART':
7962 AddToCart(idx, 'gallery');
7963 return 'exit';
7964 break;
7965 default:
7966 // all other actions (custom1..10, or anything else)
7967 var fu = G.O.fnThumbnailToolCustAction;
7968 if( fu !== null ) {
7969 typeof fu == 'function' ? fu(r.action, G.I[idx]) : window[fu](r.action, G.I[idx]);
7970 }
7971 break;
7972 }
7973 }
7974
7975 // Download an image
7976 function DownloadImage(idx) {
7977 if( G.I[idx].mediaKind != 'img' ) { return; }
7978
7979
7980 var url = G.I[idx].src;
7981
7982 if( G.I[idx].downloadURL != undefined && G.I[idx].downloadURL != '' ) {
7983 url = G.I[idx].downloadURL;
7984 }
7985
7986 var a = document.createElement('a');
7987 a.href = url;
7988 // a.download = url.split('.').pop();
7989 a.download = url.split('/').pop();
7990 a.target = '_blank';
7991 a.style.display = 'none';
7992 document.body.appendChild(a);
7993 a.click();
7994 document.body.removeChild(a);
7995
7996 }
7997
7998 // add one image to the shopping cart
7999 function AddToCart( idx, source ) {
8000 // increment quantity if already in shopping cart
8001 var found=false;
8002 for( var i=0; i<G.shoppingCart.length; i++ ) {
8003 if( G.shoppingCart[i].idx == idx ) {
8004 G.shoppingCart[i].qty++;
8005 ThumbnailBuildToolbarOneCartUpdate( G.I[idx] );
8006
8007 var fu = G.O.fnShoppingCartUpdated;
8008 if( fu !== null ) {
8009 typeof fu == 'function' ? fu(G.shoppingCart, G.I[idx], source) : window[fu](G.shoppingCart, G.I[idx], source);
8010 }
8011 TriggerCustomEvent('shoppingCartUpdated');
8012 return;
8013 }
8014 }
8015
8016 // add to shopping cart
8017 if( !found) {
8018 G.shoppingCart.push( { idx:idx, ID:G.I[idx].GetID(), qty:1} );
8019 ThumbnailBuildToolbarOneCartUpdate(G.I[idx]);
8020
8021 var fu=G.O.fnShoppingCartUpdated;
8022 if( fu !== null ) {
8023 typeof fu == 'function' ? fu(G.shoppingCart, G.I[idx], source) : window[fu](G.shoppingCart, G.I[idx], source);
8024 }
8025 TriggerCustomEvent('shoppingCartUpdated');
8026 }
8027 }
8028
8029
8030 // All thumbnails are set to unselected
8031 function ThumbnailSelectionClear() {
8032 G.GOM.nbSelected = 0;
8033 for( var i = 0, nbTn = G.GOM.items.length; i < nbTn ; i++ ) {
8034 var item = G .I[G.GOM.items[i].thumbnailIdx];
8035 if( item.selected ) {
8036 item.selected = false;
8037 var fu = G.O.fnThumbnailSelection;
8038 if( fu !== null ) {
8039 typeof fu == 'function' ? fu(item.$elt, item, G.I) : window[fu](item.$elt, item, G.I);
8040 }
8041 }
8042 item.selected = false;
8043 }
8044 }
8045
8046 function ThumbnailSelectionToggle( idx ){
8047 var item = G.I[idx];
8048 if( item.selected === true ) {
8049 ThumbnailSelectionSet(item, false);
8050 G.GOM.nbSelected--;
8051 TriggerCustomEvent('itemUnSelected');
8052 }
8053 else {
8054 ThumbnailSelectionSet(item, true);
8055 G.GOM.nbSelected++;
8056 TriggerCustomEvent('itemSelected');
8057 }
8058 }
8059
8060
8061 // this replaces ThumbnailSelection()
8062 function ThumbnailSelectionSet(item, selected ){
8063
8064 item.selected = selected;
8065
8066 ThumbnailSelectionSetIcon( item );
8067
8068 // called when the selection status of an item changed
8069 var fu=G.O.fnThumbnailSelection;
8070 if( fu !== null ) {
8071 typeof fu == 'function' ? fu(item.$elt, item, G.I) : window[fu](item.$elt, item, G.I);
8072 }
8073
8074 }
8075
8076 function ThumbnailSelectionSetIcon( item ) {
8077 if( item.$elt == null ) {
8078 // thumbnail is not built
8079 return;
8080 }
8081 var $sub = item.$getElt('.nGY2GThumbnail');
8082 var $icon = item.$getElt('.nGY2GThumbnailIconImageSelect');
8083 if( item.selected === true) {
8084 $sub.addClass('nGY2GThumbnailSubSelected');
8085 $icon.addClass('nGY2ThumbnailSelected');
8086 $icon.removeClass('nGY2ThumbnailUnselected');
8087 $icon.html(G.O.icons.thumbnailSelected);
8088 }
8089 else {
8090 $sub.removeClass('nGY2GThumbnailSubSelected');
8091 $icon.removeClass('nGY2ThumbnailSelected');
8092 $icon.addClass('nGY2ThumbnailUnselected');
8093 $icon.html(G.O.icons.thumbnailUnselected);
8094 }
8095 }
8096
8097
8098 // display a modal popup for sharing image/album
8099 function PopupShare(idx) {
8100
8101 // SEE SAMPLES: https://gist.github.com/chrisjlee/5196139
8102 // https://github.com/Julienh/Sharrre
8103
8104 var item=G.I[idx];
8105
8106 var currentURL=document.location.protocol + '//' + document.location.hostname + document.location.pathname;
8107 var newLocationHash = '#nanogallery/' + G.baseEltID + '/';
8108 if( item.kind == 'image' ) {
8109 newLocationHash += item.albumID + '/' + item.GetID();
8110 }
8111 else {
8112 newLocationHash += item.GetID();
8113 }
8114
8115 var content = '<br><br>';
8116 content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="facebook">' + G.O.icons.shareFacebook + '</div>';
8117 content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="pinterest">' + G.O.icons.sharePinterest + '</div>';
8118 content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="tumblr">' + G.O.icons.shareTumblr + '</div>';
8119 content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="twitter">' + G.O.icons.shareTwitter + '</div>';
8120 // content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="googleplus">' + G.O.icons.shareGooglePlus + '</div>';
8121 content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="vk">' + G.O.icons.shareVK + '</div>';
8122 content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="mail">' + G.O.icons.shareMail + '</div>';
8123 content += '<div class="nGY2PopupOneItem" style="text-align:center;"></div>';
8124 content += '<input class="nGY2PopupOneItemText" readonly type="text" value="' + currentURL+newLocationHash + '" style="width:100%;text-align:center;">';
8125 content += '<br>';
8126
8127 currentURL = encodeURIComponent(document.location.protocol + '//' + document.location.hostname + document.location.pathname + newLocationHash);
8128
8129 var currentTitle = item.title;
8130 var currentTn = item.thumbImg().src;
8131
8132
8133 Popup('nanogallery2 - share to:', content, 'Center');
8134
8135 G.popup.$elt.find('.nGY2PopupOneItem').on('click', function(e) {
8136 e.stopPropagation();
8137
8138 var shareURL = '';
8139 var found = true;
8140 switch(jQuery(this).attr('data-share').toUpperCase()) {
8141 case 'FACEBOOK':
8142 // <a name="fb_share" type="button" href="http://www.facebook.com/sharer.php?u={$url}&media={$imgPath}&description={$desc}" class="joinFB">Share Your Advertise</a>
8143 //window.open("https://www.facebook.com/sharer.php?u="+currentURL,"","height=368,width=600,left=100,top=100,menubar=0");
8144 shareURL = 'https://www.facebook.com/sharer.php?u=' + currentURL;
8145 break;
8146 case 'VK':
8147 shareURL = 'http://vk.com/share.php?url=' + currentURL;
8148 break;
8149 case 'GOOGLEPLUS':
8150 shareURL = "https://plus.google.com/share?url=" + currentURL;
8151 break;
8152 case 'TWITTER':
8153 // shareURL="https://twitter.com/share?url="+currentURL+"&text="+currentTitle;
8154 shareURL = 'https://twitter.com/intent/tweet?text=' + currentTitle + 'url=' + currentURL;
8155 break;
8156 case 'PINTEREST':
8157 // shareURL='https://pinterest.com/pin/create/bookmarklet/?media='+currentTn+'&url='+currentURL+'&description='+currentTitle;
8158 shareURL = 'https://pinterest.com/pin/create/button/?media=' + currentTn + '&url=' + currentURL + '&description=' + currentTitle;
8159 break;
8160 case 'TUMBLR':
8161 //shareURL='https://www.tumblr.com/widgets/share/tool/preview?caption=<strong>'+currentTitle+'</strong>&tags=nanogallery2&url='+currentURL+'&shareSource=legacy&posttype=photo&content='+currentTn+'&clickthroughUrl='+currentURL;
8162 shareURL = 'http://www.tumblr.com/share/link?url=' + currentURL + '&name=' + currentTitle;
8163 break;
8164 case 'MAIL':
8165 shareURL = 'mailto:?subject=' + currentTitle + '&body=' + currentURL;
8166 break;
8167 default:
8168 found = false;
8169 break;
8170 }
8171
8172 if( found ) {
8173 window.open(shareURL, "" , "height=550,width=500,left=100,top=100,menubar=0" );
8174 G.popup.close();
8175 // $popup.remove();
8176 }
8177
8178 });
8179 }
8180
8181 // build a modal popup
8182 function Popup(title, content, align) {
8183 var pp = '<div class="nGY2Popup" style="opacity:0;"><div class="nGY2PopupContent' + align + '">';
8184 pp += '<div class="nGY2PopupCloseButton" style="font-size:0.9em;">' + G.O.icons.buttonClose + '</div>';
8185 pp += '<div class="nGY2PopupTitle">' + title + '</div>';
8186 pp += content;
8187 pp += '</div></div>';
8188
8189 G.popup.$elt = jQuery(pp).appendTo('body');
8190 setElementOnTop( G.VOM.$viewer, G.popup.$elt);
8191
8192 G.popup.isDisplayed = true;
8193
8194 var tweenable = new NGTweenable();
8195 tweenable.tween({
8196 from: { o: 0, y: 100 },
8197 to: { o: 1, y: 0 },
8198 easing: 'easeInOutSine',
8199 duration: 250,
8200 step: function (state, att) {
8201 G.popup.$elt[0].style.opacity = state.o;
8202 G.popup.$elt[0].style[G.CSStransformName] = 'translateY(' + (state.y) + 'px)';
8203 }
8204 });
8205
8206 G.popup.$elt.find('.nGY2PopupCloseButton').on('click', function(e) {
8207 e.stopPropagation();
8208 G.popup.close();
8209 });
8210
8211 }
8212
8213
8214 function GalleryMouseEnter(e) {
8215 if( !G.VOM.viewerDisplayed && G.GOM.albumIdx != -1 ) {
8216 var r = GalleryEventRetrieveElementl(e, true);
8217 // if( r.action == 'OPEN' && r.GOMidx != -1 ) {
8218 if( r.GOMidx != -1 ) {
8219 // var target = e.target || e.srcElement;
8220 // if( target.getAttribute('class') != 'nGY2GThumbnail' ) { return; }
8221 ThumbnailHover(r.GOMidx);
8222 }
8223 }
8224 }
8225
8226 function GalleryMouseLeave(e) {
8227 if( !G.VOM.viewerDisplayed && G.GOM.albumIdx != -1 ) {
8228 var r = GalleryEventRetrieveElementl(e, true);
8229 if( r.GOMidx != -1 ) {
8230 // var target = e.target || e.srcElement;
8231 // if( target.getAttribute('class') != 'nGY2GThumbnail' ) { return; }
8232 ThumbnailHoverOut(r.GOMidx);
8233 }
8234 }
8235 }
8236
8237 function GalleryEventRetrieveElementl( e, ignoreSubItems ) {
8238 var r = { action: 'NONE', GOMidx: -1 };
8239
8240 if( e == undefined ) {
8241 return r;
8242 }
8243 var target = e.target || e.srcElement;
8244 while( target != G.$E.conTnParent[0] ) { // loop element parent up to find the thumbnail element
8245 if( jQuery(target).hasClass('nGY2GThumbnail') ) {
8246 if( r.action == 'NONE' ) {
8247 r.action = 'OPEN';
8248 }
8249 r.GOMidx = jQuery(target).data('index');
8250 return r;
8251 }
8252 // if( !ignoreSubItems && jQuery(target).hasClass('nGY2GThumbnailIcon') ) {
8253 if( !ignoreSubItems ) {
8254 var a = jQuery(target).data('ngy2action');
8255 if( a != '' && a != undefined ) {
8256 r.action = a;
8257 }
8258 }
8259 if( target.parentNode == null ) {
8260 return r;
8261 }
8262 target = target.parentNode;
8263 }
8264 return r;
8265 }
8266
8267
8268 // OPEN ONE THUMBNAIL
8269 function ThumbnailOpen( idx, ignoreSelected ) {
8270 var item = G.I[idx];
8271
8272 G.GOM.albumIdxLoading = idx; // store idx -> may be used to display loader on album thumbnail
8273
8274 var fu = G.O.fnThumbnailClicked;
8275 if( fu !== null ) {
8276 typeof fu == 'function' ? fu(item.$elt, item) : window[fu](item.$elt, item);
8277 }
8278
8279 // open URL
8280 if( item.destinationURL !== undefined && item.destinationURL.length > 0 ) {
8281 window.location = item.destinationURL;
8282 return;
8283 }
8284
8285 switch( item.kind ) {
8286 case 'image':
8287 if( ignoreSelected === false && G.GOM.nbSelected > 0 ) {
8288 ThumbnailSelectionToggle(idx);
8289 }
8290 else {
8291 // display image
8292 DisplayPhotoIdx( idx );
8293 }
8294 break;
8295 case 'album':
8296 if( ignoreSelected === false && G.GOM.nbSelected > 0 ) {
8297 ThumbnailSelectionToggle( idx );
8298 }
8299 else {
8300 if( G.O.thumbnailAlbumDisplayImage && idx != 0 ) {
8301 // display album content in lightbox
8302 DisplayFirstMediaInAlbum( idx );
8303 return;
8304 }
8305 else {
8306 // display album content in gallery
8307 DisplayAlbum('-1', item.GetID());
8308 }
8309 }
8310 break;
8311 case 'albumUp':
8312 var parent = NGY2Item.Get(G, item.albumID);
8313 DisplayAlbum('-1', parent.albumID);
8314 break;
8315 }
8316 }
8317
8318 function DisplayFirstMediaInAlbum( albumIdx ) {
8319 if( G.O.debugMode ) { console.log('#DisplayFirstPhotoInAlbum : '+ albumIdx); }
8320
8321 var item = G.I[albumIdx];
8322
8323 var l = G.I.length;
8324 for( var i = 0; i < l; i++ ) {
8325 if( G.I[i].albumID == item.GetID() ) {
8326 DisplayPhotoIdx( i );
8327 return;
8328 }
8329 }
8330
8331 // load album content
8332 AlbumGetContent( item.GetID(), DisplayFirstMediaInAlbum, albumIdx, null );
8333
8334 }
8335
8336
8337 // Open link to original image (new window)
8338 function OpenOriginal( item ) {
8339 switch( G.O.kind ) {
8340 case 'flickr':
8341 var sU = 'https://www.flickr.com/photos/' + G.O.userID + '/' + item.GetID();
8342 if( item.albumID != '0' ) {
8343 sU += '/in/album-' + item.albumID + '/';
8344 }
8345 window.open(sU, '_blank');
8346 break;
8347 case 'picasa':
8348 case 'google':
8349 case 'google2':
8350 // no more working since Google changed the access to Google Photos in 2017
8351 // var sU='https://plus.google.com/photos/'+G.O.userID+'/albums/'+item.albumID+'/'+item.GetID();
8352 // window.open(sU,'_blank');
8353 // break;
8354 default:
8355 var sU = item.responsiveURL();
8356 window.open(sU, '_blank');
8357 break;
8358 }
8359 }
8360
8361 // ########################################################
8362 // DISPLAY ONE MEDIA
8363 // with internal or external viewer
8364 // ########################################################
8365 function DisplayPhotoIdx( ngy2ItemIdx ) {
8366
8367 if( !G.O.thumbnailOpenInLightox ) { return; }
8368
8369 if( G.O.thumbnailOpenOriginal ) {
8370 // Open link to original image
8371 OpenOriginal( G.I[ngy2ItemIdx] );
8372 return;
8373 }
8374
8375 var items = [];
8376// G.VOM.currItemIdx = 0;
8377 G.VOM.content.current.vIdx = 0;
8378 G.VOM.items = [];
8379 G.VOM.albumID = G.I[ngy2ItemIdx].albumID;
8380
8381 var vimg = new VImg(ngy2ItemIdx);
8382 G.VOM.items.push(vimg);
8383 items.push(G.I[ngy2ItemIdx]);
8384 //TODO -> danger? -> pourquoi reconstruire la liste si d�j� ouvert (back/forward)
8385 var l = G.I.length;
8386 for( let idx = ngy2ItemIdx+1; idx < l ; idx++) {
8387 let item = G.I[idx];
8388 if( item.kind == 'image' && item.isToDisplay(G.VOM.albumID) && item.destinationURL == '' ) {
8389 let vimg = new VImg(idx);
8390 G.VOM.items.push(vimg);
8391 items.push(item);
8392 }
8393 }
8394 var last = G.VOM.items.length;
8395 var cnt = 1;
8396 for( let idx = 0; idx < ngy2ItemIdx ; idx++) {
8397 let item = G.I[idx];
8398 if( item.kind == 'image' && item.isToDisplay(G.VOM.albumID) && item.destinationURL == '' ) {
8399 let vimg = new VImg(idx);
8400 vimg.mediaNumber = cnt;
8401 G.VOM.items.push(vimg);
8402 items.push(item);
8403 cnt++;
8404 }
8405 }
8406 for( let i = 0; i < last; i++ ) {
8407 G.VOM.items[i].mediaNumber = cnt;
8408 cnt++;
8409 }
8410
8411 // opens media with external viewer
8412 var fu = G.O.fnThumbnailOpen;
8413 if( fu !== null ) {
8414 typeof fu == 'function' ? fu(items) : window[fu](items);
8415 return;
8416 }
8417
8418 // use internal viewer
8419 if( !G.VOM.viewerDisplayed ) {
8420 // build viewer and display
8421 LightboxOpen();
8422 }
8423 else {
8424 // viewer already displayed -> display new media in current viewer
8425 G.VOM.content.current.$media.empty();
8426 let item = G.VOM.content.current.NGY2Item();
8427 var spreloader = '<div class="nGY2ViewerMediaLoaderDisplayed"></div>';
8428 if( item.mediaKind == 'img' && item.imageWidth != 0 && item.imageHeight != 0 ) {
8429 spreloader = '<div class="nGY2ViewerMediaLoaderHidden"></div>';
8430 }
8431 G.VOM.content.current.$media.append( spreloader + item.mediaMarkup);
8432 ViewerSetMediaVisibility(G.VOM.content.next, 0);
8433 ViewerSetMediaVisibility(G.VOM.content.previous, 0);
8434 if( item.mediaKind == 'img' ) {
8435 G.VOM.ImageLoader.loadImage(VieweImgSizeRetrieved, item);
8436 }
8437 // G.VOM.$mediaCurrent.css({ opacity:0 }).attr('src','');
8438 // G.VOM.ImageLoader.loadImage(VieweImgSizeRetrieved, G.VOM.NGY2Item(0));
8439 // G.VOM.$mediaCurrent.children().eq(0).attr('src',G.emptyGif).attr('src', G.VOM.NGY2Item(0).responsiveURL());
8440 // LightboxDisplay(0, '');
8441 LightboxDisplay('');
8442 }
8443 }
8444
8445 function ViewerZoomStart() {
8446 if( G.O.viewerZoom && !G.VOM.viewerMediaIsChanged ) {
8447 var item = G.VOM.content.current.NGY2Item();
8448 if( item.mediaKind == 'img' && item.imageHeight > 0 && item.imageWidth > 0 ) {
8449 if( G.VOM.zoom.isZooming === false ) {
8450 // default zoom
8451 G.VOM.zoom.userFactor = 1;
8452 G.VOM.zoom.isZooming = true;
8453 }
8454 return true;
8455 }
8456 }
8457 return false;
8458 }
8459
8460 function ViewerZoomIn( zoomIn ) {
8461 if( zoomIn ) {
8462 // zoom in
8463 G.VOM.zoom.userFactor += 0.1;
8464 ViewerZoomMax();
8465 }
8466 else {
8467 // zoom out
8468 G.VOM.zoom.userFactor -= 0.1;
8469 ViewerZoomMin();
8470 }
8471 ViewerMediaSetPosAndZoom();
8472 }
8473
8474 function ViewerZoomMax() {
8475 if( G.VOM.zoom.userFactor > 3 ) {
8476 G.VOM.zoom.userFactor = 3;
8477 }
8478 }
8479 function ViewerZoomMin() {
8480
8481 if( G.VOM.zoom.userFactor < 0.2 ) {
8482 G.VOM.zoom.userFactor = 0.2;
8483 }
8484 }
8485
8486
8487
8488 // Set position and size of all 3 media containers
8489 function ViewerMediaSetPosAndZoom() {
8490
8491 if( !G.VOM.zoom.isZooming ) {
8492 G.VOM.zoom.userFactor = 1;
8493 }
8494 // window.ng_draf( function() {
8495 ViewerMediaSetPosAndZoomOne( G.VOM.content.current, true );
8496 ViewerMediaSetPosAndZoomOne( G.VOM.content.previous, false );
8497 ViewerMediaSetPosAndZoomOne( G.VOM.content.next, false );
8498 // });
8499 }
8500
8501
8502
8503 // Media which is not IMG -> center and set size
8504 function ViewerMediaCenterNotImg( $mediaContainer ) {
8505 var $media = $mediaContainer.children().eq(1);
8506 var h = 90;
8507 if( G.O.viewerGallery != 'none' ) { h -= 10; }
8508 if( G.O.viewerToolbar.display != 'none' ) { h -= 10; }
8509 $media.css( {'height': h+'%' });
8510 $media.css( {'width': '90%' });
8511 $media[0].style[G.CSStransformName] = 'translate(0px, "50%") ';
8512 }
8513
8514 // Set position and size of ONE media container
8515 function ViewerMediaSetPosAndZoomOne(content_item, isCurrent ) {
8516
8517 var item = content_item.NGY2Item();
8518 var $img = content_item.$media;
8519
8520
8521 if( item.mediaKind != 'img' ) {
8522 ViewerMediaCenterNotImg( $img );
8523 return;
8524 }
8525
8526 if( item.imageHeight == 0 || item.imageWidth == 0 ) {
8527 // ViewerSetMediaVisibility( item, $img, 0 );
8528 ViewerSetMediaVisibility( content_item, 0 );
8529 return;
8530 }
8531
8532 // part 1: set the image size
8533 var zoomUserFactor = isCurrent == true ? G.VOM.zoom.userFactor : 1;
8534
8535 var dpr = 1;
8536 if( G.O.viewerImageDisplay == 'bestImageQuality' ) {
8537 dpr = window.devicePixelRatio;
8538 }
8539
8540 // retrieve the base zoom factor (image fill screen)
8541 var zoomBaseFactorW = (G.VOM.window.lastWidth - G.VOM.padding.V) / (item.imageWidth / dpr);
8542 var zoomBaseFactorH = (G.VOM.window.lastHeight - G.VOM.padding.H) / (item.imageHeight / dpr);
8543 var zoomBaseFactor = Math.min(zoomBaseFactorW, zoomBaseFactorH);
8544 if( zoomBaseFactor > 1 && G.O.viewerImageDisplay != 'upscale' ) {
8545 // no upscale
8546 zoomBaseFactor = 1;
8547 }
8548
8549 var imageCurrentHeight = (item.imageHeight / dpr) * zoomUserFactor * zoomBaseFactor;
8550 var imageCurrentWidth = (item.imageWidth / dpr) * zoomUserFactor * zoomBaseFactor;
8551 $img.children().eq(1).css( {'height': imageCurrentHeight });
8552 $img.children().eq(1).css( {'width': imageCurrentWidth });
8553
8554 // retrieve posX/Y to center image
8555 var posX = 0;
8556 if( imageCurrentWidth > G.VOM.window.lastWidth ) {
8557 posX = -(imageCurrentWidth - G.VOM.window.lastWidth)/2;
8558 }
8559 var posY = 0;
8560 // if( imageCurrentHeight > G.VOM.window.lastHeight ) {
8561 // posY = ( imageCurrentHeight - G.VOM.window.lastHeight ) / 2;
8562 // }
8563 // posY = 0; // actually, it seems that the image is always centered vertically -> so no need to to anything
8564
8565 // Part 2: set the X/Y position (for zoom/pan)
8566 if( isCurrent ) {
8567 if( !G.VOM.zoom.isZooming ) {
8568 G.VOM.panPosX = 0;
8569 G.VOM.panPosY = 0;
8570 }
8571 G.VOM.zoom.posX = posX;
8572 G.VOM.zoom.posY = posY;
8573 ViewerImagePanSetPosition(G.VOM.panPosX, G.VOM.panPosY, $img, false);
8574 }
8575 // else {
8576 //$img[0].style[G.CSStransformName]= 'translate3D('+ posX+'px, '+ posY+'px, 0) ';
8577 // }
8578 else {
8579 // set the pan position of each media container
8580 ViewerMediaPanX( G.VOM.swipePosX );
8581 $img.children().eq(1)[0].style[G.CSStransformName]= 'translate(0px, 0px) rotate('+ item.rotationAngle +'deg)';
8582 }
8583
8584 }
8585
8586 // position the image depending on the zoom factor and the pan X/Y position
8587 // IMG is the only media kind supporting zoom/pan
8588 function ViewerImagePanSetPosition(posX, posY, imageContainer, savePosition ) {
8589 if( savePosition ) {
8590 G.VOM.panPosX = posX;
8591 G.VOM.panPosY = posY;
8592 }
8593
8594 posX += G.VOM.zoom.posX;
8595 posY += G.VOM.zoom.posY;
8596
8597 // imageContainer.children().eq(1)[0].style[G.CSStransformName]= 'translate('+ posX + 'px, '+ posY + 'px)';
8598 imageContainer.children().eq(1)[0].style[G.CSStransformName]= 'translate('+ posX + 'px, '+ posY + 'px) rotate('+ G.VOM.content.current.NGY2Item().rotationAngle +'deg)';
8599
8600
8601 }
8602
8603
8604 // LIGHTBOX
8605 // display media with internal viewer
8606 function LightboxOpen( idx ) {
8607
8608 // G.VOM.viewerDisplayed = true;
8609 G.GOM.firstDisplay = false;
8610
8611 // remove scrollbar and add right margin with same width as the scrollbar to avoid page reflow
8612 jQuery('head').append('<style id="nGY2_body_scrollbar_style" type="text/css">.nGY2_body_scrollbar{margin-right: ' + (window.innerWidth - document.documentElement.clientWidth) + 'px;}</style>');
8613 jQuery("body").addClass("nGY2_body_scrollbar");
8614
8615
8616 G.VOM.$baseCont = jQuery('<div class="nGY2 nGY2ViewerContainer" style="opacity:1"></div>').appendTo('body');
8617
8618 SetViewerTheme();
8619
8620 G.VOM.$viewer = jQuery('<div class="nGY2Viewer" style="opacity:0" itemscope itemtype="http://schema.org/ImageObject"></div>').appendTo( G.VOM.$baseCont );
8621 G.VOM.$viewer.css({ msTouchAction: 'none', touchAction: 'none' }); // avoid pinch zoom
8622
8623 if( idx == undefined ) {
8624 G.VOM.content.current.vIdx = 0;
8625 }
8626 else {
8627 G.VOM.content.current.vIdx = idx;
8628 }
8629 G.VOM.content.previous.vIdx = G.VOM.IdxNext();
8630 G.VOM.content.next.vIdx = G.VOM.IdxPrevious();
8631
8632 var sMedia = '<div class="nGY2ViewerMediaPan"><div class="nGY2ViewerMediaLoaderDisplayed"></div>' + G.VOM.content.previous.NGY2Item().mediaMarkup + '</div>'; // previous media
8633 sMedia += '<div class="nGY2ViewerMediaPan"><div class="nGY2ViewerMediaLoaderDisplayed"></div>' + G.VOM.content.current.NGY2Item().mediaMarkup + '</div>'; // current media
8634 sMedia += '<div class="nGY2ViewerMediaPan"><div class="nGY2ViewerMediaLoaderDisplayed"></div>' + G.VOM.content.next.NGY2Item().mediaMarkup + '</div>'; // next media
8635
8636 var sNav = '';
8637 var iconP = G.O.icons.viewerImgPrevious;
8638 if( iconP != undefined && iconP != '') {
8639 sNav += '<div class="nGY2ViewerAreaPrevious ngy2viewerToolAction" data-ngy2action="previous">' + iconP + '</div>';
8640 }
8641 var iconN = G.O.icons.viewerImgNext;
8642 if( iconN != undefined && iconN != '') {
8643 sNav += '<div class="nGY2ViewerAreaNext ngy2viewerToolAction" data-ngy2action="next">' + iconN + '</div>';
8644 }
8645
8646 G.VOM.$content = jQuery('<div class="nGY2ViewerContent">' + sMedia + sNav + '</div>').appendTo( G.VOM.$viewer );
8647
8648 G.VOM.$buttonLeft = G.VOM.$content.find('.nGY2ViewerAreaPrevious');
8649 G.VOM.$buttonRight = G.VOM.$content.find('.nGY2ViewerAreaNext');
8650
8651 var $mediaPan = G.VOM.$content.find('.nGY2ViewerMediaPan');
8652 G.VOM.content.previous.$media = $mediaPan.eq(0); // pointer to previous media container
8653 G.VOM.content.current.$media = $mediaPan.eq(1); // pointer to current media container
8654 G.VOM.content.next.$media = $mediaPan.eq(2); // pointer to next media container
8655
8656 // position next/previous media
8657 var vP = G.GOM.cache.viewport;
8658 G.VOM.content.previous.$media[0].style[G.CSStransformName] = 'translate(-' + vP.w + 'px, 0px)';
8659 G.VOM.content.next.$media[0].style[G.CSStransformName] = 'translate(' + vP.w + 'px, 0px)';
8660
8661
8662 G.VOM.ImageLoader.loadImage( VieweImgSizeRetrieved, G.VOM.content.current.NGY2Item() );
8663 G.VOM.ImageLoader.loadImage( VieweImgSizeRetrieved, G.VOM.content.previous.NGY2Item() );
8664 G.VOM.ImageLoader.loadImage( VieweImgSizeRetrieved, G.VOM.content.next.NGY2Item() );
8665
8666 G.VOM.padding.H = parseInt(G.VOM.$content.css("padding-left")) + parseInt(G.VOM.$content.css("padding-right"));
8667 G.VOM.padding.V = parseInt(G.VOM.$content.css("padding-top")) + parseInt(G.VOM.$content.css("padding-bottom"));
8668
8669 // build media toolbar container
8670 var vtbBg1 = '';
8671 var vtbBg2 = ' toolbarBackground';
8672 if( G.O.viewerToolbar.fullWidth ) {
8673 vtbBg1 = ' toolbarBackground';
8674 vtbBg2 = '';
8675 }
8676 var vtbAlign = 'text-align:center;';
8677 switch ( G.O.viewerToolbar.align ) {
8678 case 'left':
8679 vtbAlign = 'text-align:left;';
8680 break;
8681 case 'right':
8682 vtbAlign = 'text-align:right;';
8683 break;
8684 }
8685 var sTB = '<div class="toolbarContainer nGEvent' + vtbBg1 + '" style="visibility:' +(G.O.viewerToolbar.display ? "visible" : "hidden")+';'+vtbAlign+'"><div class="toolbar nGEvent' + vtbBg2 + '"></div></div>';
8686 G.VOM.$toolbar = jQuery(sTB).appendTo(G.VOM.$viewer);
8687
8688 if( G.VOM.toolbarMode == 'min' || (G.O.viewerToolbar.autoMinimize > 0 && G.O.viewerToolbar.autoMinimize >= G.GOM.cache.viewport.w) ) {
8689 ViewerToolbarForVisibilityMin();
8690 }
8691 else {
8692 ViewerToolbarForVisibilityStd();
8693 }
8694
8695 // top-left toolbar
8696 var sTopLeft = '<div class="nGY2ViewerToolsTopLeft nGEvent"><div class="toolbar nGEvent">';
8697 var sTL = G.O.viewerTools.topLeft.split(',');
8698 for( var i = 0, sTLL = sTL.length; i < sTLL; i++) {
8699 sTopLeft += ToolbarAddElt( sTL[i] );
8700 }
8701 sTopLeft += '</div></div>';
8702 G.VOM.$toolbarTL = jQuery(sTopLeft).appendTo(G.VOM.$viewer);
8703
8704 // top-right toolbar
8705 var sTopRight = '<div class="nGY2ViewerToolsTopRight nGEvent"><div class="toolbar nGEvent">';
8706 var sTR = G.O.viewerTools.topRight.split(',');
8707 for( var i = 0, sTRL = sTR.length; i < sTRL; i++) {
8708 sTopRight += ToolbarAddElt( sTR[i] );
8709 }
8710 sTopRight += '</div></div>';
8711 G.VOM.$toolbarTR = jQuery(sTopRight).appendTo(G.VOM.$viewer);
8712
8713 // set the events handler on the toolbars
8714 ViewerToolsOn();
8715
8716 // Go to fullscreen mode
8717 if( ngscreenfull.enabled && G.O.viewerFullscreen ) {
8718 ngscreenfull.request();
8719 G.VOM.viewerIsFullscreen=true;
8720 }
8721
8722 // Gallery
8723 LightboxGalleryBuild();
8724
8725 setElementOnTop('', G.VOM.$viewer);
8726 ResizeLightbox(true);
8727 G.VOM.gallery.Resize();
8728 G.VOM.timeImgChanged = new Date().getTime();
8729
8730 // viewer display transition
8731 G.VOM.$toolbarTL.css('opacity', 0);
8732 G.VOM.$toolbarTR.css('opacity', 0);
8733 G.VOM.$buttonLeft.css('opacity', 0);
8734 G.VOM.$buttonRight.css('opacity', 0);
8735 if( G.O.viewerGallery != 'none' ) { G.VOM.gallery.$elt.css('opacity', 0); }
8736 G.VOM.$content.css('opacity', 0);
8737 G.VOM.$toolbarTR[0].style[G.CSStransformName] = 'translateY(-40px) ';
8738 G.VOM.$toolbarTL[0].style[G.CSStransformName] = 'translateY(-40px) ';
8739 G.VOM.$buttonLeft[0].style[G.CSStransformName] = 'translateX(-40px) ';
8740 G.VOM.$buttonRight[0].style[G.CSStransformName] = 'translateX(40px) ';
8741
8742 // STEP 1: display main container, including media
8743 new NGTweenable().tween({
8744 from: { opacity: 0, posY: G.VOM.window.lastHeight*.5 },
8745 to: { opacity: 1, posY: 0 },
8746 delay: 10,
8747 duration: 450,
8748 easing: 'easeInOutQuint',
8749 step: function (state) {
8750 // lightbox
8751 G.VOM.$viewer.css('opacity', state.opacity);
8752 G.VOM.$viewer[0].style[G.CSStransformName] = 'translateY(' + (state.posY) + 'px) ';
8753
8754 // media in lightbox
8755 G.VOM.$content.css('opacity', state.opacity);
8756 }
8757 });
8758
8759
8760 // STEP 2: display tools, left/right navigation buttons, gallery
8761 new NGTweenable().tween({
8762 from: { posY: -40, opacity: 0, scale: 3 },
8763 to: { posY: 0, opacity: 1, scale: 1 },
8764 delay: 300,
8765 duration: 400,
8766 easing: 'easeInOutQuint',
8767 step: function (state) {
8768
8769 // tools
8770 G.VOM.$toolbarTR[0].style[G.CSStransformName] = 'translateY(' + (state.posY) + 'px) ';
8771 G.VOM.$toolbarTL[0].style[G.CSStransformName] = 'translateY(' + (state.posY) + 'px) ';
8772 G.VOM.$buttonLeft[0].style[G.CSStransformName] = 'translateX(' + (state.posY) + 'px) ';
8773 G.VOM.$buttonRight[0].style[G.CSStransformName] = 'translateX(' + (-state.posY) + 'px) ';
8774
8775 // gallery
8776 if( G.O.viewerGallery != 'none' ) {
8777 G.VOM.gallery.$elt.css({ opacity: state.opacity });
8778 G.VOM.gallery.$elt[0].style[G.CSStransformName] = 'scale('+state.scale+')';
8779 }
8780
8781 },
8782 finish: function() {
8783 G.VOM.viewerDisplayed = true;
8784 ViewerMediaPanX(0);
8785 ViewerSetEvents();
8786
8787 LightboxDisplay('');
8788
8789 if( G.O.slideshowAutoStart ) {
8790 G.VOM.playSlideshow = false;
8791 SlideshowToggle();
8792 }
8793
8794 ViewerToolsUnHide();
8795 LightboxDisplayFinalize('');
8796 }
8797 });
8798
8799
8800
8801
8802 // stop click propagation on media ==> if the user clicks outside of an media, the viewer is closed
8803 // --> no more supported since v2.0.0
8804 // G.VOM.$viewer.find('img').on('click', function (e) { e.stopPropagation(); });
8805
8806
8807 // ViewerMediaPanX(0);
8808 // ViewerSetEvents();
8809
8810 // LightboxDisplay('');
8811
8812 // if( G.O.slideshowAutoStart ) {
8813 // G.VOM.playSlideshow = false;
8814 // SlideshowToggle();
8815 // }
8816 }
8817
8818 function ViewerEvents() {
8819 if( !G.VOM.viewerDisplayed || G.VOM.viewerMediaIsChanged ) {
8820 // if( !G.VOM.viewerDisplayed || G.VOM.viewerMediaIsChanged || G.VOM.content.current.NGY2Item().mediaKind != 'img') {
8821 // ignore fired event if viewer not displayed or if currently changed (or if current media not an image)
8822 return false;
8823 }
8824 return true;
8825 }
8826
8827 // VIEWER - BUILD THE THUMBNAILS GALLERY
8828 function LightboxGalleryBuild() {
8829
8830 G.VOM.gallery.firstDisplay = true;
8831
8832 if( G.O.viewerGallery != 'none' ) {
8833
8834 var tw = G.O.viewerGalleryTWidth;
8835 var th = G.O.viewerGalleryTHeight;
8836 var gutter = 2;
8837
8838 var t = '';
8839 for( var i=0; i< G.VOM.items.length; i++) {
8840 var idx = G.VOM.items[i].ngy2ItemIdx;
8841 var o = G.I[idx];
8842 var src = (o.thumbImg().src).replace(/'/g, "%27"); // replace single quote with %27
8843 t += '<div class="nGY2VThumbnail" style="width:'+tw+'px;height:'+th+'px;left:'+i*(tw+gutter*2)+'px;background-image: url(&apos;'+src+'&apos;);" data-ngy2_lightbox_thumbnail="true" data-ngy2_idx="' + idx + '" data-ngy2_vidx="' + i + '" ></div>';
8844 }
8845 G.VOM.gallery.gwidth = (tw+2*gutter) * G.VOM.items.length;
8846 G.VOM.gallery.oneTmbWidth = tw+2*gutter;
8847 var tc = "<div class='nGY2VThumbnailContainer' style='height:"+(th+gutter*2)+"px;left:0;width:"+G.VOM.gallery.gwidth+"px;' data-ngy2_lightbox_gallery='true'>" + t + "</div>";
8848 G.VOM.gallery.$elt = jQuery('<div class="nGY2viewerGallery" style="display: inline-block;height:'+(th+gutter*2)+'px;left:0;right:0;">'+ tc +'</div>').appendTo(G.VOM.$viewer);
8849 G.VOM.gallery.$tmbCont = G.VOM.gallery.$elt.find('.nGY2VThumbnailContainer')
8850
8851 G.VOM.gallery.Resize();
8852 G.VOM.gallery.SetThumbnailActive();
8853
8854 }
8855 }
8856
8857
8858 // Lightbox gesture handling
8859 function ViewerSetEvents() {
8860
8861 if( G.VOM.hammertime == null ) {
8862
8863 G.VOM.hammertime = new NGHammer.Manager(G.VOM.$baseCont[0], {
8864 // domEvents: true,
8865 recognizers: [
8866 [NGHammer.Pinch, { enable: true }],
8867 [NGHammer.Pan, { direction: NGHammer.DIRECTION_ALL }]
8868 ]
8869 });
8870
8871 // PAN
8872 G.VOM.hammertime.on('pan', function(ev) {
8873 if( !ViewerEvents() ) { return; }
8874
8875
8876 if( G.VOM.panMode == 'off' ) {
8877 // PAN START -> determine the element to pan
8878 if( ev.target.dataset.ngy2_lightbox_thumbnail != undefined || ev.target.dataset.ngy2_lightbox_gallery != undefined ){
8879 G.VOM.panMode = 'gallery';
8880 }
8881 else {
8882 if( G.VOM.zoom.isZooming ) {
8883 G.VOM.panMode = 'zoom';
8884 }
8885 else {
8886 G.VOM.panMode = 'media';
8887 }
8888 }
8889 }
8890
8891 // PAN the determined element
8892 switch( G.VOM.panMode ) {
8893 case 'zoom':
8894 // pan zoomed image
8895 ViewerImagePanSetPosition(G.VOM.panPosX + ev.deltaX, G.VOM.panPosY + ev.deltaY, G.VOM.content.current.$media, false);
8896 G.VOM.toolsHide();
8897 break;
8898
8899 case 'media':
8900 if( Math.abs(ev.deltaY) > G.VOM.panThreshold && Math.abs(ev.deltaX) < G.VOM.panThreshold && !G.VOM.panXOnly ) {
8901 // pan viewer down/up to close the lightbox
8902 ViewerMediaPanX( 0 );
8903 var dist = 0;
8904 if( ev.deltaY < 0 ) {
8905 // pan up
8906 dist = Math.max( ev.deltaY, -200);
8907 }
8908 else {
8909 // pan down
8910 dist = Math.min( ev.deltaY, 200);
8911 }
8912 G.VOM.$viewer[0].style[G.CSStransformName] = 'translateY(' + dist + 'px) ';
8913 G.VOM.$viewer.css('opacity', 1-Math.abs(dist)/200/2);
8914 }
8915 else {
8916 // pan media left/right
8917 if( Math.abs(ev.deltaX) > G.VOM.panThreshold ) {
8918 G.VOM.panXOnly = true;
8919 }
8920 ViewerMediaPanX( ev.deltaX );
8921 G.VOM.$viewer[0].style[G.CSStransformName] = 'translateY(0px)';
8922 G.VOM.$viewer.css('opacity', 1);
8923 }
8924 break;
8925
8926 case 'gallery':
8927 G.VOM.gallery.PanGallery( ev.deltaX );
8928 break;
8929 }
8930
8931 });
8932
8933 // PAN END
8934 G.VOM.hammertime.on('panend', function(ev) {
8935 if( !ViewerEvents() ) { return; }
8936
8937 switch( G.VOM.panMode ) {
8938 case 'zoom':
8939 // PAN END in image zoom mode
8940 G.VOM.timeImgChanged = new Date().getTime();
8941 ViewerImagePanSetPosition( G.VOM.panPosX+ev.deltaX, G.VOM.panPosY+ev.deltaY, G.VOM.content.current.$media, true);
8942 break;
8943 case 'media':
8944 var panY = false;
8945 if( !G.VOM.panXOnly ) {
8946 if( Math.abs(ev.deltaY) > 50 && Math.abs(ev.deltaX) < 50 ) {
8947 // close viewer
8948 LightboxClose();
8949 panY = true;
8950 }
8951 }
8952 if( !panY ) {
8953 if( Math.abs( ev.deltaX ) < 50 ) {
8954 ViewerMediaPanX(0);
8955 }
8956 else {
8957 ev.deltaX > 50 ? DisplayPreviousMedia( Math.abs(ev.velocityX) ) : DisplayNextMedia( Math.abs(ev.velocityX) );
8958 }
8959 }
8960 G.VOM.panXOnly = false;
8961 break;
8962 case 'gallery':
8963 // PAN END on thumbnail gallery
8964 G.VOM.gallery.posX += ev.deltaX;
8965 G.VOM.gallery.PanGallery( 0 );
8966 G.VOM.gallery.PanGalleryEnd( ev.velocityX );
8967 break;
8968 }
8969
8970 G.VOM.panMode = 'off';
8971 });
8972
8973
8974 // ZOOM FEATURE ENABLED
8975 if( G.O.viewerZoom ) {
8976
8977 G.VOM.hammertime.add( new NGHammer.Tap({ event: 'doubletap', taps: 2, interval: 250 }) );
8978 G.VOM.hammertime.add( new NGHammer.Tap({ event: 'singletap' }) );
8979 G.VOM.hammertime.get('doubletap').recognizeWith('singletap');
8980 G.VOM.hammertime.get('singletap').requireFailure('doubletap');
8981
8982 // single tap -> next/previous media
8983 G.VOM.hammertime.on('singletap', function(ev) {
8984
8985 if( !ViewerEvents() ) { return; }
8986
8987 // Gallery on viewer -> click/touch on one thumbnail -> display corresponding image
8988 if( ev.target.dataset.ngy2_lightbox_thumbnail != undefined ){
8989
8990 var idx = parseInt(ev.target.dataset.ngy2_idx);
8991 var vidx = parseInt(ev.target.dataset.ngy2_vidx);
8992
8993 if( !isNaN(idx) && vidx != G.VOM.content.current.vIdx ) {
8994
8995 if( vidx > G.VOM.content.current.vIdx ) {
8996 TriggerCustomEvent('lightboxNextImage');
8997
8998 // replace the next media with selected media
8999 G.VOM.content.next.$media.empty();
9000 var nextItem = G.I[idx];
9001 G.VOM.content.next.vIdx = vidx;
9002 let spreloader = '<div class="nGY2ViewerMediaLoaderDisplayed"></div>';
9003 if( nextItem.mediaKind == 'img' && nextItem.imageWidth != 0 && nextItem.imageHeight != 0 ) {
9004 spreloader = '<div class="nGY2ViewerMediaLoaderHidden"></div>';
9005 }
9006 G.VOM.content.next.$media.append( spreloader + nextItem.mediaMarkup );
9007 if( nextItem.mediaKind == 'img' ) {
9008 G.VOM.ImageLoader.loadImage(VieweImgSizeRetrieved, nextItem);
9009 }
9010 else {
9011 ViewerMediaCenterNotImg( G.VOM.content.next.$media );
9012 }
9013 LightboxDisplay('nextImage');
9014
9015 }
9016 else {
9017 TriggerCustomEvent('lightboxPreviousImage');
9018
9019 // replace the previous media with selected media
9020 G.VOM.content.previous.$media.empty();
9021 var previousItem = G.I[idx];
9022 G.VOM.content.previous.vIdx = vidx;
9023 let spreloader = '<div class="nGY2ViewerMediaLoaderDisplayed"></div>';
9024 if( previousItem.mediaKind == 'img' && previousItem.imageWidth != 0 && previousItem.imageHeight != 0 ) {
9025 spreloader = '<div class="nGY2ViewerMediaLoaderHidden"></div>';
9026 }
9027 G.VOM.content.previous.$media.append( spreloader + previousItem.mediaMarkup );
9028 if( previousItem.mediaKind == 'img' ) {
9029 G.VOM.ImageLoader.loadImage(VieweImgSizeRetrieved, previousItem);
9030 }
9031 else {
9032 ViewerMediaCenterNotImg( G.VOM.content.previous.$media );
9033 }
9034 LightboxDisplay('previousImage');
9035 }
9036 return;
9037 }
9038 }
9039
9040
9041 StopPropagationPreventDefault(ev.srcEvent);
9042 if( G.VOM.toolbarsDisplayed == false ) {
9043 debounce( ViewerToolsUnHide, 100, false)();
9044 G.VOM.singletapTime = new Date().getTime();
9045 }
9046 else {
9047 // toolbars are displayed -> display next/previous media
9048 if( (new Date().getTime()) - G.VOM.singletapTime < 400 ) { return; } // to avoid conflict with MOUSEMOVE event
9049
9050 if( G.VOM.content.current.NGY2Item().mediaKind == 'img' && ev.target.className.indexOf('nGY2ViewerMedia') !== -1 ) {
9051 var x =0;
9052 if( ev.srcEvent instanceof MouseEvent ) {
9053 x = ev.srcEvent.pageX;
9054 }
9055 else {
9056 x = ev.srcEvent.changedTouches[0].pageX;
9057 }
9058 if( x < (G.GOM.cache.viewport.w/2) ) {
9059 DisplayPreviousMedia();
9060 }
9061 else {
9062 DisplayNextMedia();
9063 }
9064 }
9065 }
9066 });
9067
9068 // double tap -> zoom
9069 G.VOM.hammertime.on('doubletap', function(ev) {
9070 if( !ViewerEvents() ) { return; }
9071 StopPropagationPreventDefault(ev.srcEvent);
9072
9073 if( ev.target.className.indexOf('nGY2ViewerMedia') !== -1 ) {
9074 // double tap only on image
9075 if( G.VOM.zoom.isZooming ) {
9076 G.VOM.zoom.isZooming = false;
9077 // G.VOM.zoom.userFactor = 1;
9078 ResizeLightbox(true);
9079 }
9080 else {
9081 if( ViewerZoomStart() ) {
9082 G.VOM.zoom.userFactor = 1.5;
9083 ViewerMediaSetPosAndZoom();
9084 }
9085 }
9086 }
9087 });
9088
9089 // pinch end
9090 G.VOM.hammertime.on('pinchend', function(ev) {
9091 ev.srcEvent.stopPropagation();
9092 ev.srcEvent.preventDefault(); // cancel mouseenter event
9093 G.VOM.timeImgChanged = new Date().getTime();
9094 });
9095 G.VOM.hammertime.on('pinch', function(ev) {
9096 ev.srcEvent.stopPropagation();
9097 ev.srcEvent.preventDefault(); // cancel mouseenter event
9098
9099 if( ViewerZoomStart() ) {
9100 G.VOM.zoom.userFactor = ev.scale;
9101 ViewerZoomMax();
9102 ViewerZoomMin();
9103 ViewerMediaSetPosAndZoom(); // center media
9104 }
9105 });
9106 }
9107
9108
9109 else {
9110 // ZOOM FEATURE DISABLED
9111
9112 G.VOM.hammertime.add( new NGHammer.Tap({ event: 'singletap' }) );
9113
9114 // click/tap on image to go to next/previous one
9115 // G.VOM.hammertime.on('tap', function(ev) {
9116 G.VOM.hammertime.on('singletap', function(ev) {
9117 if( !ViewerEvents() ) { return; }
9118 StopPropagationPreventDefault( ev.srcEvent );
9119 if( G.VOM.toolbarsDisplayed == false ){
9120 // display tools on tap if hidden
9121 debounce( ViewerToolsUnHide, 100, false)();
9122 G.VOM.singletapTime = new Date().getTime();
9123 }
9124 else {
9125 // toolbars are displayed -> display next/previous media
9126 if( (new Date().getTime()) - G.VOM.singletapTime < 400 ) { return; } // to avoid conflict with MOUSEMOVE event
9127 if( ev.target.className.indexOf('nGY2ViewerMedia') !== -1 ) {
9128 var x = 0;
9129 if( ev.srcEvent instanceof MouseEvent ) {
9130 x = ev.srcEvent.pageX;
9131 }
9132 else {
9133 x = ev.srcEvent.changedTouches[0].pageX;
9134 }
9135 if( x < (G.GOM.cache.viewport.w/2) ) {
9136 DisplayPreviousMedia();
9137 }
9138 else {
9139 DisplayNextMedia();
9140 }
9141 }
9142 }
9143
9144 });
9145 }
9146 }
9147 }
9148
9149
9150 function StopPropagationPreventDefault(e) {
9151 e.stopPropagation();
9152 e.preventDefault();
9153 }
9154
9155 // Hide toolbars on user inactivity
9156 function ViewerToolsHide() {
9157 if( G.VOM.viewerDisplayed ) {
9158 G.VOM.toolbarsDisplayed = false;
9159 ViewerToolsOpacity(0);
9160 }
9161 }
9162
9163 function ViewerToolsUnHide() {
9164 if( G.VOM.viewerDisplayed ) {
9165 G.VOM.toolbarsDisplayed = true;
9166 ViewerToolsOpacity(1);
9167 G.VOM.toolsHide(); // re-init delay before hide tools+gallery
9168 }
9169 }
9170
9171 function ViewerToolsOpacity( op ) {
9172 if( G.VOM.$toolbar != null ) {
9173 G.VOM.$toolbar.css('opacity', op);
9174 }
9175 if( G.VOM.$toolbarTL != null ) {
9176 G.VOM.$toolbarTL.css('opacity', op);
9177 }
9178 if( G.VOM.$toolbarTR != null ) {
9179 G.VOM.$toolbarTR.css('opacity', op);
9180 }
9181
9182 // next/previous
9183 G.VOM.$content.find('.nGY2ViewerAreaNext').css('opacity', op);
9184 G.VOM.$content.find('.nGY2ViewerAreaPrevious').css('opacity', op);
9185
9186 // gallery
9187 // G.VOM.gallery.$elt.css('opacity', op);
9188 }
9189
9190
9191
9192 function ViewerToolsOn() {
9193 // removes all events
9194 G.VOM.$viewer.off('touchstart click', '.ngy2viewerToolAction', ViewerToolsAction);
9195
9196 // action button
9197 G.VOM.$viewer.on('touchstart click', '.ngy2viewerToolAction', ViewerToolsAction);
9198 }
9199
9200
9201 // Actions of the button/elements
9202 function ViewerToolsAction(e) {
9203 // delay to avoid twice handling on smartphone/tablet (both touchstart click events are fired)
9204 if( (new Date().getTime()) - G.timeLastTouchStart < 300 ) { return; }
9205 G.timeLastTouchStart = new Date().getTime();
9206
9207 var $this = $(this);
9208 var ngy2action = $this.data('ngy2action');
9209 if( ngy2action == undefined ) { return; }
9210 switch( ngy2action ) {
9211 case 'next':
9212 StopPropagationPreventDefault(e);
9213 DisplayNextMedia();
9214 break;
9215 case 'previous':
9216 StopPropagationPreventDefault(e);
9217 DisplayPreviousMedia();
9218 break;
9219 case 'playPause':
9220 e.stopPropagation();
9221 SlideshowToggle();
9222 break;
9223 case 'zoomIn':
9224 StopPropagationPreventDefault(e);
9225 if( ViewerZoomStart() ) { ViewerZoomIn( true ); }
9226 break;
9227 case 'zoomOut':
9228 StopPropagationPreventDefault(e);
9229 if( ViewerZoomStart() ) { ViewerZoomIn( false ); }
9230 break;
9231 case 'minimize':
9232 // toggle toolbar visibility
9233 StopPropagationPreventDefault(e);
9234 if( G.VOM.toolbarMode == 'std' ) {
9235 ViewerToolbarForVisibilityMin();
9236 }
9237 else {
9238 ViewerToolbarForVisibilityStd();
9239 }
9240 break;
9241 case 'fullScreen':
9242 // Toggle viewer fullscreen mode on/off
9243 e.stopPropagation();
9244 if( ngscreenfull.enabled ) {
9245 ngscreenfull.toggle();
9246 }
9247 break;
9248 case 'info':
9249 e.stopPropagation();
9250 ItemDisplayInfo( G.VOM.content.current.NGY2Item() );
9251 break;
9252 case 'close':
9253 StopPropagationPreventDefault(e);
9254 if( (new Date().getTime()) - G.VOM.timeImgChanged < 400 ) { return; }
9255 LightboxClose();
9256 break;
9257 case 'download':
9258 StopPropagationPreventDefault(e);
9259 DownloadImage(G.VOM.items[G.VOM.content.current.vIdx].ngy2ItemIdx);
9260 break;
9261 case 'share':
9262 StopPropagationPreventDefault(e);
9263 PopupShare(G.VOM.items[G.VOM.content.current.vIdx].ngy2ItemIdx);
9264 break;
9265 case 'linkOriginal':
9266 StopPropagationPreventDefault(e);
9267 OpenOriginal( G.VOM.content.current.NGY2Item() );
9268 break;
9269 case 'rotateLeft':
9270 StopPropagationPreventDefault(e);
9271 ViewerImageRotate(-90);
9272 break;
9273 case 'rotateRight':
9274 StopPropagationPreventDefault(e);
9275 ViewerImageRotate(90);
9276 break;
9277 case 'shoppingcart':
9278 StopPropagationPreventDefault(e);
9279 AddToCart( G.VOM.items[G.VOM.content.current.vIdx].ngy2ItemIdx, 'lightbox');
9280 break;
9281 }
9282
9283 // custom button
9284 var fu = G.O.fnImgToolbarCustClick;
9285 if( ngy2action.indexOf('custom') == 0 && fu !== null ) {
9286 typeof fu == 'function' ? fu(ngy2action, $this, G.VOM.content.current.NGY2Item() ) : window[fu](ngy2action, $this, G.VOM.content.current.NGY2Item() );
9287 }
9288 }
9289
9290 // rotate displayed image
9291 function ViewerImageRotate( angle ) {
9292 var item = G.VOM.content.current.NGY2Item();
9293 if( item.mediaKind == 'img' ) {
9294 item.rotationAngle += angle;
9295 item.rotationAngle = item.rotationAngle % 360;
9296 if( item.rotationAngle < 0 ) {
9297 item.rotationAngle += 360;
9298 }
9299 ViewerMediaPanX( 0 );
9300 ViewerMediaSetPosAndZoomOne( G.VOM.content.current, true );
9301 }
9302 }
9303
9304
9305 // Display photo infos in popup/modal
9306 function ItemDisplayInfo( ng2item ) {
9307
9308 var content = '<div class="nGY2PopupOneItem">' + ng2item.title + '</div>';
9309 content += '<div class="nGY2PopupOneItemText">' + ng2item.description + '</div>';
9310 if( ng2item.author != '' ) {
9311 content += '<div class="nGY2PopupOneItemText">' + G.O.icons.user + ' ' + ng2item.author + '</div>';
9312 }
9313 if( ng2item.exif.model != '' ) {
9314 content += '<div class="nGY2PopupOneItemText">' + G.O.icons.config + ' ' + ng2item.exif.model + '</div>';
9315 }
9316 var sexif = G.O.icons.picture + ':';
9317 if( ng2item.exif.flash != '' || ng2item.exif.focallength != '' || ng2item.exif.fstop != '' || ng2item.exif.exposure != '' || ng2item.exif.iso != '' || ng2item.exif.time != '' ) {
9318 sexif += '<br>';
9319 sexif += ng2item.exif.flash == '' ? '' : ' &nbsp; ' + ng2item.exif.flash;
9320 sexif += ng2item.exif.focallength == '' ? '' : ' &nbsp; ' + ng2item.exif.focallength+'mm';
9321 sexif += ng2item.exif.fstop == '' ? '' : ' &nbsp; f' + ng2item.exif.fstop;
9322 sexif += ng2item.exif.exposure == '' ? '' : ' &nbsp; ' + ng2item.exif.exposure+'s';
9323 sexif += ng2item.exif.iso == '' ? '' : ' &nbsp; ' + ng2item.exif.iso+' ISO';
9324 if( ng2item.exif.time != '' ) {
9325 // var date = new Date(parseInt(ng2item.exif.time));
9326 // sexif += ' &nbsp; '+date.toLocaleDateString();
9327 sexif += ' &nbsp; ' + ng2item.exif.time;
9328 }
9329 }
9330 else {
9331 sexif += ' n/a';
9332 }
9333 content += '<div class="nGY2PopupOneItemText">' + sexif + '</div>';
9334
9335 if( ng2item.exif.location != '' ) {
9336 content += '<div class="nGY2PopupOneItemText">'+G.O.icons.location+' <a href="http://maps.google.com/maps?z=12&t=m&q='+encodeURIComponent(ng2item.exif.location)+'" target="_blank">'+ng2item.exif.location+'</a></div>';
9337 // embed google map in iframe (no api key required)
9338 content += '<iframe width="300" height="150" frameborder="0" scrolling="no" marginheight="0" marginwidth="0" src="https://maps.google.com/maps?&amp;t=m&amp;q='+encodeURIComponent( ng2item.exif.location ) +'&amp;output=embed"></iframe>';
9339 }
9340 else {
9341 content += '<div class="nGY2PopupOneItemText">' + G.O.icons.location + ': n/a</div>';
9342 }
9343
9344 var r = { title: G.O.icons.viewerInfo, content: content };
9345
9346 // callback
9347 var fu = G.O.fnPopupMediaInfo;
9348 if( fu !== null ) {
9349 typeof fu == 'function' ? r=fu(ng2item, r.title, r.content) : r=window[fu](ng2item, r.title, r.content);
9350 }
9351
9352
9353 Popup( r.title, r.content, 'Left');
9354
9355 }
9356
9357
9358
9359 function ToolbarAddElt( elt ) {
9360 var r = '<div class="ngbt ngy2viewerToolAction ',
9361 e=elt.replace(/^\s+|\s+$/g, ''); // remove trailing/leading whitespace
9362 switch( e ) {
9363 case 'minimizeButton':
9364 case 'minimize':
9365 var ic = G.O.icons.viewerToolbarMin;
9366 if( G.VOM.toolbarMode == 'min' ) {
9367 ic = G.O.icons.viewerToolbarStd;
9368 }
9369 r += 'minimizeButton nGEvent" data-ngy2action="minimize">'+ic+'</div>';
9370 break;
9371 case 'previousButton':
9372 case 'previous':
9373 r += 'previousButton nGEvent" data-ngy2action="previous">'+ G.O.icons.viewerPrevious +'</div>';
9374 break;
9375 case 'pageCounter':
9376 r += 'pageCounter nGEvent"></div>';
9377 break;
9378 case 'nextButton':
9379 case 'next':
9380 r += 'nextButton nGEvent" data-ngy2action="next">'+ G.O.icons.viewerNext +'</div>';
9381 break;
9382 case 'playPauseButton':
9383 case 'playPause':
9384 r += 'playButton playPauseButton nGEvent" data-ngy2action="playPause">'+ G.O.icons.viewerPlay +'</div>';
9385 break;
9386 case 'rotateLeft':
9387 r += 'rotateLeftButton nGEvent" data-ngy2action="rotateLeft">'+ G.O.icons.viewerRotateLeft +'</div>';
9388 break;
9389 case 'rotateRight':
9390 r += 'rotateRightButton nGEvent" data-ngy2action="rotateRight">'+ G.O.icons.viewerRotateRight +'</div>';
9391 break;
9392 case 'downloadButton':
9393 case 'download':
9394 r += 'downloadButton nGEvent" data-ngy2action="download">'+ G.O.icons.viewerDownload +'</div>';
9395 break;
9396 case 'zoomButton':
9397 case 'zoom':
9398 r += 'nGEvent" data-ngy2action="zoomIn">'+ G.O.icons.viewerZoomIn +'</div><div class="ngbt ngy2viewerToolAction nGEvent" data-ngy2action="zoomOut">'+ G.O.icons.viewerZoomOut +'</div>';
9399 break;
9400 case 'fullscreenButton':
9401 case 'fullscreen':
9402 var s = G.O.icons.viewerFullscreenOn;
9403 if( ngscreenfull.enabled && G.VOM.viewerIsFullscreen ) {
9404 s = G.O.icons.viewerFullscreenOff;
9405 }
9406 r += 'setFullscreenButton fullscreenButton nGEvent" data-ngy2action="fullScreen">'+s+'</div>';
9407 break;
9408 case 'infoButton':
9409 case 'info':
9410 r += 'infoButton nGEvent" data-ngy2action="info">'+ G.O.icons.viewerInfo +'</div>';
9411 break;
9412 case 'linkOriginalButton':
9413 case 'linkOriginal':
9414 r += 'linkOriginalButton nGEvent" data-ngy2action="linkOriginal">' + G.O.icons.viewerLinkOriginal + '</div>';
9415 break;
9416 case 'closeButton':
9417 case 'close':
9418 r += 'closeButton nGEvent" data-ngy2action="close">'+ G.O.icons.buttonClose +'</div>';
9419 break;
9420 case 'shareButton':
9421 case 'share':
9422 r += 'nGEvent" data-ngy2action="share">'+ G.O.icons.viewerShare +'</div>';
9423 break;
9424 case 'label':
9425 r += '"><div class="label"><div class="title nGEvent" itemprop="name"></div><div class="description nGEvent" itemprop="description"></div></div></div>';
9426 break;
9427 case 'shoppingcart':
9428 r += 'closeButton nGEvent" data-ngy2action="shoppingcart">'+ G.O.icons.viewerShoppingcart +'</div>';
9429 break;
9430 default:
9431 // custom button
9432 if( e.indexOf('custom') == 0 ) {
9433 var t = '';
9434 // content to display from custom script
9435 var fu = G.O.fnImgToolbarCustInit;
9436 if( fu !== null ) {
9437 typeof fu == 'function' ? fu(e) : window[fu](e);
9438 }
9439 if( t == undefined || t == '' ) {
9440 // content from icons
9441 var n = e.substring(6);
9442 t = G.O.icons['viewerCustomTool'+n];
9443 }
9444 r += 'ngy2CustomBtn ' + e + ' nGEvent" data-ngy2action="' + e + '">' + t + '</div>';
9445 }
9446 else {
9447 r = '';
9448 }
9449 break;
9450 }
9451 return r;
9452 }
9453
9454
9455 // toggle slideshow mode on/off
9456 function SlideshowToggle(){
9457 if( G.VOM.playSlideshow ) {
9458 window.clearTimeout(G.VOM.playSlideshowTimerID);
9459 G.VOM.playSlideshow = false;
9460 G.VOM.$viewer.find('.playPauseButton').html(G.O.icons.viewerPlay);
9461 }
9462 else {
9463 G.VOM.playSlideshow = true;
9464 DisplayNextMedia();
9465 G.VOM.$viewer.find('.playPauseButton').html(G.O.icons.viewerPause);
9466 }
9467 }
9468
9469 function ViewerToolbarForVisibilityStd() {
9470 G.VOM.toolbarMode = 'std';
9471
9472 var sTB = '';
9473 var t = G.O.viewerToolbar.standard.split(',');
9474 for( var i = 0, lt = t.length; i < lt; i++) {
9475 sTB += ToolbarAddElt( t[i] );
9476 }
9477 G.VOM.$toolbar.find('.toolbar').html(sTB);
9478 ViewerToolbarElementContent();
9479 }
9480
9481 function ViewerToolbarForVisibilityMin() {
9482 if( G.O.viewerToolbar.minimized == undefined || G.O.viewerToolbar.minimized == '' ) {
9483 ViewerToolbarForVisibilityStd();
9484 }
9485 else {
9486 G.VOM.toolbarMode = 'min';
9487 var sTB = '';
9488 var t = G.O.viewerToolbar.minimized.split(',');
9489 for( var i = 0, lt = t.length; i < lt; i++) {
9490 sTB += ToolbarAddElt( t[i] );
9491 }
9492 G.VOM.$toolbar.find('.toolbar').html(sTB);
9493 ViewerToolbarElementContent();
9494 }
9495 }
9496
9497 function ViewerToolbarElementContent() {
9498
9499 var vomIdx = G.VOM.content.current.vIdx;
9500 if( vomIdx == null ) { return; }
9501
9502 var item = G.VOM.content.current.NGY2Item();
9503
9504 // LABEL
9505 var setTxt = false;
9506 // set title
9507 if( item.title !== undefined && item.title != '' ) {
9508 G.VOM.$viewer.find('.ngy2viewerToolAction').find('.title').html(item.title);
9509 setTxt = true;
9510 }
9511 else {
9512 G.VOM.$viewer.find('.ngy2viewerToolAction').find('.title').html('');
9513 }
9514 // set description
9515 if( item.description !== undefined && item.description != '' ) {
9516 G.VOM.$viewer.find('.ngy2viewerToolAction').find('.description').html(item.description);
9517 setTxt = true;
9518 }
9519 else {
9520 G.VOM.$viewer.find('.ngy2viewerToolAction').find('.description').html('');
9521 }
9522
9523 if( setTxt ) {
9524 G.VOM.$viewer.find('.ngy2viewerToolAction').find('.label').show();
9525 }
9526 else {
9527 G.VOM.$viewer.find('.ngy2viewerToolAction').find('.label').hide();
9528 }
9529
9530 // set page number
9531 var viewerMaxImages = G.VOM.items.length;
9532 if( viewerMaxImages > 0 ) {
9533 G.VOM.$viewer.find('.pageCounter').html((G.VOM.items[vomIdx].mediaNumber)+'/'+viewerMaxImages);
9534 }
9535
9536 // custom elements
9537 var $cu = G.VOM.$viewer.find('.ngy2CustomBtn');
9538 var fu = G.O.fnImgToolbarCustDisplay;
9539 if( $cu.length > 0 && fu !== null ) {
9540 typeof fu == 'function' ? fu($cu, item) : window[fu]($cu, item);
9541 }
9542
9543 // set event handlers again
9544 ViewerToolsOn();
9545 }
9546
9547 // Pan the media container in the lightbox (left/right)
9548 function ViewerMediaPanX( posX ) {
9549 G.VOM.swipePosX = posX;
9550 if( G.CSStransformName == null ) {
9551 // no pan if CSS transform not supported
9552 // G.VOM.$mediaCurrent.css({ left: posX });
9553 }
9554 else {
9555
9556 // pan left/right the current media
9557 // window.ng_draf( function() {
9558 G.VOM.content.current.$media[0].style[G.CSStransformName] = 'translate(' + posX + 'px, 0px)';
9559 // });
9560
9561 var itemPrevious = G.VOM.content.previous.NGY2Item();
9562 var itemNext = G.VOM.content.next.NGY2Item();
9563
9564 // next/previous media
9565 if( G.O.imageTransition.startsWith('SWIPE') ) {
9566 if( itemPrevious.mediaTransition() ) {
9567 ViewerSetMediaVisibility(G.VOM.content.previous, 1);
9568 }
9569 if( itemNext.mediaTransition() ) {
9570 ViewerSetMediaVisibility(G.VOM.content.next, 1);
9571 }
9572
9573 var sc = Math.min( Math.max( Math.abs(posX) / G.VOM.window.lastWidth, .8), 1);
9574 if( G.O.imageTransition == 'SWIPE' ) { sc = 1; }
9575
9576 if( posX > 0 ) {
9577 let dir = G.VOM.window.lastWidth;
9578 if( itemPrevious.mediaTransition() ) {
9579 // window.ng_draf( function() {
9580 G.VOM.content.previous.$media[0].style[G.CSStransformName] = 'translate(' + (-dir + posX) + 'px, 0px) scale(' + sc + ')';
9581 // });
9582 }
9583 if( itemNext.mediaTransition() ) {
9584 // window.ng_draf( function() {
9585 G.VOM.content.next.$media[0].style[G.CSStransformName] = 'translate(' + (dir) + 'px, 0px) scale(' + sc + ')';
9586 // });
9587 }
9588 }
9589 else {
9590 let dir = -G.VOM.window.lastWidth;
9591 if( itemNext.mediaTransition() ) {
9592 // window.ng_draf( function() {
9593 G.VOM.content.next.$media[0].style[G.CSStransformName] = 'translate(' + (-dir + posX) + 'px, 0px) scale(' + sc + ')';
9594 // });
9595 }
9596 if( itemPrevious.mediaTransition() ) {
9597 // window.ng_draf( function() {
9598 G.VOM.content.previous.$media[0].style[G.CSStransformName] = 'translate(' + (dir) + 'px, 0px) scale(' + sc + ')';
9599 // });
9600 }
9601 }
9602 }
9603
9604
9605 if( G.O.imageTransition == 'SLIDEAPPEAR' ) {
9606 G.VOM.content.previous.$media[0].style[G.CSStransformName] = '';
9607 G.VOM.content.next.$media[0].style[G.CSStransformName] = '';
9608 if( posX < 0 ) {
9609 let o = (-posX) / G.VOM.window.lastWidth;
9610 if( itemNext.mediaTransition() ) {
9611 ViewerSetMediaVisibility(G.VOM.content.next, o);
9612 }
9613 if( itemPrevious.mediaTransition() ) {
9614 ViewerSetMediaVisibility(G.VOM.content.previous, 0);
9615 }
9616 }
9617 else {
9618 let o = posX / G.VOM.window.lastWidth;
9619 if( itemPrevious.mediaTransition() ) {
9620 ViewerSetMediaVisibility(G.VOM.content.previous, o);
9621 }
9622 if( itemNext.mediaTransition() ) {
9623 ViewerSetMediaVisibility(G.VOM.content.next, 0);
9624 }
9625 }
9626 }
9627 }
9628 }
9629
9630 // Display next image
9631 function DisplayNextMedia( velocity ) {
9632 velocity = velocity || 0;
9633
9634 if( G.VOM.viewerMediaIsChanged || ((new Date().getTime()) - G.VOM.timeImgChanged < 300) ) { return; }
9635
9636 TriggerCustomEvent('lightboxNextImage');
9637 LightboxDisplay('nextImage', velocity);
9638 };
9639
9640 // Display previous image
9641 function DisplayPreviousMedia( velocity ) {
9642 velocity = velocity || 0;
9643
9644 if( G.VOM.viewerMediaIsChanged || ((new Date().getTime()) - G.VOM.timeImgChanged < 300) ) { return; }
9645 if( G.VOM.playSlideshow ) {
9646 SlideshowToggle();
9647 }
9648
9649 TriggerCustomEvent('lightboxPreviousImage');
9650 LightboxDisplay( 'previousImage', velocity);
9651 };
9652
9653
9654
9655 // Display image (with animation if possible)
9656 function LightboxDisplay( displayType, velocity ) {
9657
9658 velocity = velocity || 0;
9659
9660 if( G.O.debugMode && console.timeline ) { console.timeline('nanogallery2_viewer'); }
9661
9662 if( G.VOM.playSlideshow ) { window.clearTimeout( G.VOM.playSlideshowTimerID ); }
9663
9664 var current_content_item = null;
9665 var new_content_item = null;
9666
9667 G.VOM.timeImgChanged = new Date().getTime();
9668 G.VOM.viewerMediaIsChanged = true;
9669 G.VOM.zoom.isZooming = false;
9670 ResizeLightbox(true);
9671
9672 switch( displayType ) {
9673 case '':
9674 current_content_item = G.VOM.content.current;
9675 new_content_item = G.VOM.content.current;
9676 break;
9677 case 'previousImage':
9678 current_content_item = G.VOM.content.current;
9679 new_content_item = G.VOM.content.previous;
9680 break;
9681 default:
9682 current_content_item = G.VOM.content.current;
9683 new_content_item = G.VOM.content.next;
9684 }
9685
9686 // SetLocationHash( next_ng2item.albumID, next_ng2item.GetID() );
9687 SetLocationHash( new_content_item.NGY2Item().albumID, new_content_item.NGY2Item().GetID() );
9688
9689 if( displayType == '' ) {
9690 // first media -> no transition -> exit
9691 return;
9692 }
9693
9694 // animation duration is proportional of the remaining distance
9695 var vP = G.GOM.cache.viewport;
9696 var t_easing = '';
9697 var t_dur = 500 * (vP.w - Math.abs(G.VOM.swipePosX)) / vP.w;
9698 if( velocity > 0 ) {
9699 // velocity = pixels/millisecond
9700 t_dur = Math.min( (vP.w - Math.abs(G.VOM.swipePosX)) / velocity, t_dur);
9701 t_easing = 'linear'; // use linear to avoid a slow-down in the transition after user swipe
9702 }
9703
9704
9705 // animate the image transition between 2 medias
9706
9707 if( G.CSStransformName == null ) {
9708 // no CSS transform support -> no animation
9709 ViewerSetMediaVisibility(new_content_item, 1);
9710 ViewerSetMediaVisibility(current_content_item, 1);
9711 LightboxDisplayFinalize(displayType);
9712 }
9713 else {
9714 switch( G.O.imageTransition ) {
9715 case 'SWIPE':
9716 case 'SWIPE2':
9717 var dir = ( displayType == 'nextImage' ? - vP.w : vP.w );
9718 new_content_item.$media[0].style[G.CSStransformName] = 'translate(' + (-dir) + 'px, 0px) '
9719
9720 if( velocity == 0 ) {
9721 t_easing = G.O.imageTransition == 'swipe' ? 'easeInOutSine' : 'easeOutCubic';
9722 }
9723
9724 ViewerSetMediaVisibility(G.VOM.content.current, 1);
9725 G.VOM.content.current.$media[0].style[G.CSStransformName] = 'translate(0px, 0px)';
9726 ViewerSetMediaVisibility(new_content_item, 1);
9727
9728 new NGTweenable().tween({
9729 from: { t: G.VOM.swipePosX },
9730 to: { t: (displayType == 'nextImage' ? - vP.w : vP.w) },
9731 attachment: { dT: displayType, new_content_item: new_content_item, dir: dir, media_transition: new_content_item.NGY2Item().mediaTransition()},
9732 // delay: 30,
9733 duration: t_dur,
9734 easing: ( t_easing ),
9735 step: function (state, att) {
9736 // current displayed media
9737 G.VOM.content.current.$media[0].style[G.CSStransformName] = 'translate(' + state.t + 'px, 0px)';
9738
9739 // new media
9740 if( att.media_transition ) {
9741 // new media supports transition
9742 var sc = Math.min( Math.max( (Math.abs(state.t)) / G.VOM.window.lastWidth, .8), 1);
9743 if( G.O.imageTransition == 'SWIPE' ) { sc = 1; }
9744 att.new_content_item.$media[0].style[G.CSStransformName] = 'translate(' + (-att.dir+state.t) + 'px, 0px) scale(' + sc + ')';
9745 }
9746 },
9747 finish: function (state, att) {
9748 G.VOM.content.current.$media[0].style[G.CSStransformName] = '';
9749 ViewerSetMediaVisibility(G.VOM.content.current, 0);
9750 att.new_content_item.$media[0].style[G.CSStransformName] = '';
9751 LightboxDisplayFinalize(att.dT);
9752 }
9753 });
9754 break;
9755
9756 case 'SLIDEAPPEAR':
9757 default:
9758 // var dir=(displayType == 'nextImage' ? - vP.w : vP.w);
9759 var op = (Math.abs(G.VOM.swipePosX)) / G.VOM.window.lastWidth;
9760 new_content_item.$media[0].style[G.CSStransformName] = '';
9761 if( velocity == 0 ) {
9762 t_easing ='easeInOutSine';
9763 }
9764 new NGTweenable().tween({
9765 from: { o: op, t: G.VOM.swipePosX },
9766 to: { o: 1, t: (displayType == 'nextImage' ? - vP.w : vP.w) },
9767 attachment: { dT: displayType, new_content_item:new_content_item, media_transition: new_content_item.NGY2Item().mediaTransition() },
9768 delay: 30,
9769 duration: t_dur,
9770 easing: t_easing,
9771 step: function (state, att) {
9772 // current media - translate
9773 G.VOM.content.current.$media[0].style[G.CSStransformName]= 'translate('+state.t+'px, 0px)';
9774
9775 // new media - opacity
9776 if( att.media_transition ) {
9777 // new media supports transition
9778 ViewerSetMediaVisibility(att.new_content_item, state.o);
9779 }
9780 },
9781 finish: function (state, att) {
9782 G.VOM.content.current.$media[0].style[G.CSStransformName]= '';
9783 LightboxDisplayFinalize(att.dT);
9784 }
9785 });
9786 break;
9787 }
9788 }
9789
9790 }
9791
9792
9793 function LightboxDisplayFinalize( displayType ) {
9794
9795 var newVomIdx = 0;
9796 switch( displayType ) {
9797 case '':
9798 // first media to display in lightbox
9799 newVomIdx = G.VOM.content.current.vIdx;
9800 break;
9801 case 'previousImage':
9802 // previous media
9803 newVomIdx = G.VOM.content.previous.vIdx;
9804 break;
9805 default:
9806 // next media
9807 newVomIdx = G.VOM.content.next.vIdx;
9808 }
9809
9810
9811
9812 G.VOM.content.current.vIdx = newVomIdx;
9813 G.VOM.content.next.vIdx = G.VOM.IdxNext();
9814 G.VOM.content.previous.vIdx = G.VOM.IdxPrevious();
9815 G.VOM.gallery.Resize();
9816 G.VOM.gallery.SetThumbnailActive();
9817
9818 var ngy2item = G.VOM.content.current.NGY2Item();
9819
9820 ViewerToolbarElementContent();
9821 if( G.O.debugMode && console.timeline ) { console.timelineEnd('nanogallery2_viewer'); }
9822
9823 var fu=G.O.fnImgDisplayed;
9824 if( fu !== null ) {
9825 typeof fu == 'function' ? fu(ngy2item) : window[fu](ngy2item);
9826 }
9827
9828 G.VOM.swipePosX = 0;
9829 if( displayType != '' ) {
9830 // not on first media display
9831 G.VOM.content.current.$media.removeClass('imgCurrent');
9832
9833 var $tmp = G.VOM.content.current.$media;
9834 switch( displayType ) {
9835 case 'nextImage':
9836 G.VOM.content.current.$media = G.VOM.content.next.$media;
9837 G.VOM.content.next.$media = $tmp;
9838 break;
9839 case 'previousImage':
9840 G.VOM.content.current.$media = G.VOM.content.previous.$media;
9841 G.VOM.content.previous.$media = $tmp;
9842 break;
9843 }
9844 }
9845
9846 G.VOM.content.current.$media.addClass('imgCurrent');
9847
9848 // re-sort the media containers --> current on top
9849 var $pans = G.VOM.$content.find('.nGY2ViewerMediaPan');
9850 G.VOM.content.current.$media.insertAfter($pans.last());
9851
9852 if( ngy2item.mediaKind == 'img' && ngy2item.imageWidth == 0 ) {
9853 ViewerSetMediaVisibility(G.VOM.content.current, 0);
9854 }
9855 else {
9856 G.VOM.content.current.$media.children().eq(0).attr('class', 'nGY2ViewerMediaLoaderHidden'); // hide preloader
9857 ViewerSetMediaVisibility(G.VOM.content.current, 1);
9858 }
9859
9860
9861 // set the new NEXT media
9862 G.VOM.content.next.$media.empty();
9863 var nextItem = G.VOM.content.next.NGY2Item();
9864 var spreloader = '<div class="nGY2ViewerMediaLoaderDisplayed"></div>';
9865 if( nextItem.mediaKind == 'img' && nextItem.imageWidth != 0 && nextItem.imageHeight != 0 ) {
9866 spreloader = '<div class="nGY2ViewerMediaLoaderHidden"></div>';
9867 }
9868 G.VOM.content.next.$media.append( spreloader + nextItem.mediaMarkup );
9869 ViewerSetMediaVisibility(G.VOM.content.next, 0);
9870 ViewerSetMediaVisibility(G.VOM.content.previous, 0);
9871 if( nextItem.mediaKind == 'img' ) {
9872 G.VOM.ImageLoader.loadImage(VieweImgSizeRetrieved, nextItem);
9873 }
9874 else {
9875 ViewerMediaCenterNotImg( G.VOM.content.next.$media );
9876 }
9877
9878 // set the new PREVIOUS media
9879 G.VOM.content.previous.$media.empty();
9880 var previousItem = G.VOM.content.previous.NGY2Item();
9881 spreloader = '<div class="nGY2ViewerMediaLoaderDisplayed"></div>';
9882 if( previousItem.mediaKind == 'img' && previousItem.imageWidth != 0 && previousItem.imageHeight != 0 ) {
9883 spreloader = '<div class="nGY2ViewerMediaLoaderHidden"></div>';
9884 }
9885 G.VOM.content.previous.$media.append( spreloader + previousItem.mediaMarkup );
9886 ViewerSetMediaVisibility(G.VOM.content.previous, 0);
9887 ViewerSetMediaVisibility(G.VOM.content.next, 0);
9888 if( previousItem.mediaKind == 'img' ) {
9889 G.VOM.ImageLoader.loadImage( VieweImgSizeRetrieved, previousItem );
9890 }
9891 else {
9892 ViewerMediaCenterNotImg( G.VOM.content.previous.$media );
9893 }
9894
9895
9896 // slideshow mode - wait until image is loaded to start the delay for next image
9897 if( G.VOM.playSlideshow ) {
9898 G.VOM.content.current.$media.children().eq(1).ngimagesLoaded().always( function( instance ) {
9899 if( G.VOM.playSlideshow ) {
9900 // in the meantime the user could have stopped the slideshow
9901 G.VOM.playSlideshowTimerID = window.setTimeout( function(){ DisplayNextMedia(); }, G.VOM.slideshowDelay );
9902 }
9903 });
9904 }
9905
9906 // close viewer when user clicks outside of the image
9907 // G.VOM.$mediaCurrent.on("click",function(e){
9908 // e.stopPropagation();
9909 // if( (new Date().getTime()) - G.VOM.timeImgChanged < 400 ) { return; }
9910 // StopPropagationPreventDefault(e);
9911 // LightboxClose(G.VOM.currItemIdx);
9912 // return false;
9913 // });
9914
9915 ResizeLightbox();
9916
9917 G.VOM.viewerMediaIsChanged = false;
9918 TriggerCustomEvent('lightboxImageDisplayed');
9919
9920 }
9921
9922
9923 // Is fired as soon as the size of an image has been retrieved (the download may not be finished)
9924 function VieweImgSizeRetrieved(w, h, item, n) {
9925
9926 item.imageWidth = w;
9927 item.imageHeight = h;
9928
9929 // image sized retrieved for currently displayed media
9930 if( G.VOM.content.current.NGY2Item() == item ) {
9931 // it is the current displayed media
9932 G.VOM.content.current.$media.children().eq(0).attr('class', 'nGY2ViewerMediaLoaderHidden'); // hide preloader
9933 ViewerSetMediaVisibility(G.VOM.content.current, 1);
9934 G.VOM.zoom.userFactor = 1;
9935 }
9936
9937 if( G.VOM.content.next.NGY2Item() == item ) { // next media
9938 G.VOM.content.next.$media.children().eq(0).attr('class', 'nGY2ViewerMediaLoaderHidden'); // hide preloader
9939 }
9940 if( G.VOM.content.previous.NGY2Item() == item ) { // previous media
9941 G.VOM.content.previous.$media.children().eq(0).attr('class', 'nGY2ViewerMediaLoaderHidden'); // hide preloader
9942 }
9943
9944 ViewerMediaSetPosAndZoom();
9945
9946 }
9947
9948 // Viewer - Set the visibility of the media and it's container
9949 // function ViewerSetMediaVisibility(item, $media, opacity ) {
9950 function ViewerSetMediaVisibility( content_item, opacity ) {
9951
9952 var item = content_item.NGY2Item();
9953 var $media = content_item.$media;
9954
9955 if( item.mediaKind == 'img' && item.imageWidth == 0 ) {
9956 // do not display image if width is unknown (--> callback will set the width when know)
9957 // setting opacity to 0 is not enough -> it is mandatory to change also the visibility to hidden to avoid responds to events (click/touch)
9958 // $media.children().css({ opacity: 0, visibility: 'hidden' });
9959 $media.children().eq(1).css({ opacity: 0, visibility: 'hidden' }); // hide media
9960 // $media.css({ opacity: 0, visibility: 'hidden' });
9961 return;
9962 }
9963
9964 if( opacity == 0 ) {
9965 // setting opacity to 0 is not enough -> it is mandatory to change also the visibility to hidden to avoid responds to events (click/touch)
9966 // $media.css({ opacity: 0, visibility: 'hidden' });
9967 $media.children().css({ opacity: 0, visibility: 'hidden' }); // hide media
9968 }
9969 else {
9970 // $media.css({ opacity: opacity, visibility: 'visible' });
9971 $media.children().css({ opacity: opacity, visibility: 'visible' }); // display media
9972 }
9973 }
9974
9975
9976 // Close the internal lightbox
9977 function LightboxClose( vomIdx ) {
9978
9979
9980 if( vomIdx == undefined ) {
9981 vomIdx = G.VOM.content.current.vIdx;
9982 }
9983
9984 G.VOM.viewerMediaIsChanged = false;
9985
9986 if( G.VOM.viewerDisplayed ) {
9987
9988 // set scrollbar visible again
9989 jQuery("body").removeClass("nGY2_body_scrollbar");
9990 jQuery("#nGY2_body_scrollbar_style").remove();
9991
9992 if( G.VOM.playSlideshow ) {
9993 window.clearTimeout( G.VOM.playSlideshowTimerID );
9994 G.VOM.playSlideshow = false;
9995 }
9996
9997 G.VOM.hammertime.destroy();
9998 G.VOM.hammertime = null;
9999
10000 if( ngscreenfull.enabled && G.VOM.viewerIsFullscreen ) {
10001 G.VOM.viewerIsFullscreen = false;
10002 ngscreenfull.exit();
10003 }
10004
10005 // G.VOM.$baseCont.hide(0).off().show(0).html('').remove();
10006 // G.VOM.$baseCont.remove(); // does not work... (?)
10007 jQuery('.nGY2ViewerContainer').remove();
10008 G.VOM.$baseCont = null;
10009 G.VOM.viewerDisplayed = false;
10010
10011 if( G.O.lightboxStandalone ) { return; }
10012
10013 if( G.O.thumbnailAlbumDisplayImage ) {
10014 // content of album displayed directly in lightbox (no gallery display for album content)
10015 if( vomIdx == null ) {
10016 // lightbox closed with browser back-button
10017 // the gallery is already displayed
10018 }
10019 else {
10020 var item = G.I[G.VOM.items[vomIdx].ngy2ItemIdx];
10021 var parent = NGY2Item.Get(G, item.albumID);
10022 if( G.GOM.albumIdx != parent.albumID ) {
10023 // display album only if not already displayed
10024 DisplayAlbum('-1', parent.albumID);
10025 }
10026 else {
10027 GalleryResize();
10028 SetLocationHash( '', '' );
10029 ThumbnailHoverReInitAll();
10030 }
10031 }
10032 // DisplayAlbum( '-', G.I[G.VOM.items[vomIdx].ngy2ItemIdx].albumID );
10033 }
10034 else {
10035 if( vomIdx != null ) {
10036 if( G.GOM.albumIdx == -1 ) {
10037 // album not displayed --> display gallery
10038 DisplayAlbum( '', G.I[G.VOM.items[vomIdx].ngy2ItemIdx].albumID );
10039 }
10040 else {
10041 GalleryResize();
10042 SetLocationHash( G.I[G.VOM.items[vomIdx].ngy2ItemIdx].albumID, '' );
10043 ThumbnailHoverReInitAll();
10044 }
10045 }
10046 }
10047 G.VOM.timeImgChanged = new Date().getTime();
10048 }
10049 }
10050
10051
10052 // Lightbox resized -> reposition elements
10053 function ResizeLightbox( forceUpdate ) {
10054 forceUpdate = typeof forceUpdate !== 'undefined' ? forceUpdate : false;
10055
10056 if( G.VOM.$toolbar === null ) { return; } // viewer build not finished
10057
10058
10059 // window.requestAnimationFrame( function() { // synchronize with screen
10060 var windowsW = G.VOM.$viewer.width();
10061 var windowsH = G.VOM.$viewer.height();
10062 var $elt = G.VOM.content.current.$media.children().eq(1);
10063 if( $elt == null || G.VOM.content.current.vIdx == -1 ) { return; }
10064
10065 if( !forceUpdate && G.VOM.window.lastWidth == windowsW && G.VOM.window.lastHeight == windowsH ) { return; }
10066
10067 G.VOM.window.lastWidth = windowsW;
10068 G.VOM.window.lastHeight = windowsH;
10069
10070 // var $tb = G.VOM.$toolbar.find('.toolbar');
10071 // var tb_OHt = $tb.outerHeight(true);
10072
10073
10074 var galleryHeight = 0;
10075 var cBottom = 0;
10076 // Height of the thumbnails gallery
10077 if( G.O.viewerGallery != 'none' ) {
10078 galleryHeight = G.O.viewerGalleryTHeight + 10;
10079 }
10080 if( G.O.viewerGallery == 'bottom' ) {
10081 cBottom = galleryHeight;
10082 }
10083
10084
10085 switch( G.O.viewerToolbar.position ) {
10086 case 'top':
10087 case 'topOverImage':
10088 G.VOM.$content.css({height: windowsH, width: windowsW, top: 0 });
10089 G.VOM.$toolbar.css({top: 0, bottom: ''});
10090 break;
10091 // case 'top':
10092 // windowsH -= tb_OHt;
10093 // G.VOM.$content.css({height: windowsH, width: windowsW, top: tb_OHt });
10094 // G.VOM.$toolbar.css({top: 0});
10095 // break;
10096 case 'bottom':
10097 case 'bottomOverImage':
10098 default:
10099 windowsH -= cBottom;
10100 G.VOM.$content.css({height: windowsH, width: windowsW, bottom: -cBottom, top: 0 });
10101 G.VOM.$toolbar.css({bottom: galleryHeight});
10102 break;
10103 // case 'bottom':
10104 // default:
10105 // windowsH -= tb_OHt;
10106 // G.VOM.$content.css({ width: windowsW, top: 0, bottom: tb_OHt });
10107 // G.VOM.$toolbar.css({bottom: galleryHeight});
10108 // break;
10109 }
10110
10111
10112 if( !G.VOM.viewerMediaIsChanged && G.VOM.zoom.isZooming ) {
10113 ViewerMediaSetPosAndZoom();
10114 }
10115 else {
10116 if( !G.VOM.zoom.isZooming && ( G.VOM.zoom.userFactor != 0 || G.VOM.panPosX != 0 || G.VOM.panPosY != 0 || G.VOM.zoom.posX != 0 || G.VOM.zoom.posY != 0 )) {
10117 // animate image zoom factor and position back to initial values
10118 G.VOM.zoom.isZooming= true; // activate zooming temporarily
10119 new NGTweenable().tween({
10120 from: { userFactor: G.VOM.zoom.userFactor, panPosX: G.VOM.panPosX, panPosY: G.VOM.panPosY, zoomPosX: G.VOM.zoom.posX, zoomPosY: G.VOM.zoom.posY },
10121 to: { userFactor: 1, panPosX: 0, panPosY: 0, zoomPosX: 0, zoomPosY: 0 },
10122 easing: 'easeInOutSine',
10123 delay: 0,
10124 duration: 150,
10125 step: function (state) {
10126 G.VOM.zoom.userFactor = state.userFactor;
10127 G.VOM.panPosX = state.panPosX;
10128 G.VOM.panPosY = state.panPosY;
10129 G.VOM.zoom.posX = state.zoomPosX;
10130 G.VOM.zoom.posY = state.zoomPosY;
10131 ViewerMediaSetPosAndZoom();
10132 },
10133 finish: function (state) {
10134 G.VOM.zoom.isZooming=false;
10135 }
10136 });
10137
10138 }
10139 else {
10140 G.VOM.zoom.userFactor = 1;
10141 G.VOM.zoom.isZooming = false;
10142 G.VOM.panPosX = 0;
10143 G.VOM.panPosY = 0;
10144 G.VOM.zoom.posX = 0;
10145 G.VOM.zoom.posY = 0;
10146 ViewerMediaSetPosAndZoom();
10147 }
10148 }
10149 }
10150
10151 // Retrieve the first parent element which is scrollable
10152 // source: ncubica - https://stackoverflow.com/questions/35939886/find-first-scrollable-parent
10153 // returns null if nothing found
10154 function getScrollableParent (node) {
10155 const regex = /(auto|scroll)/;
10156 const parents = (_node, ps) => {
10157 if (_node.parentNode === null) { return ps; }
10158 return parents(_node.parentNode, ps.concat([_node]));
10159 };
10160
10161 const style = (_node, prop) => getComputedStyle(_node, null).getPropertyValue(prop);
10162 const overflow = _node => style(_node, 'overflow') + style(_node, 'overflow-y') + style(_node, 'overflow-x');
10163 const scroll = _node => regex.test(overflow(_node));
10164
10165 const scrollParent = (_node) => {
10166 if (!(_node instanceof HTMLElement || _node instanceof SVGElement)) {
10167 return undefined;
10168 }
10169
10170 const ps = parents(_node.parentNode, []);
10171
10172 for (let i = 0; i < ps.length; i += 1) {
10173 if( ps[i] === document.body ) {
10174 return null;
10175 }
10176 if (scroll(ps[i])) {
10177 return ps[i];
10178 }
10179 }
10180
10181 return document.scrollingElement || document.documentElement;
10182 };
10183
10184 return scrollParent(node);
10185 };
10186
10187
10188
10189 /** @function BuildSkeleton */
10190 /** Build the gallery structure **/
10191 function BuildSkeleton() {
10192
10193
10194 // store markup if defined
10195 // var $elements = G.$E.base.children('a');
10196 var $elements = G.$E.base.children();
10197 if( $elements.length > 0 ) {
10198 G.O.$markup = $elements;
10199 }
10200
10201 if( !G.O.lightboxStandalone ) {
10202 G.$E.base.text('');
10203 G.$E.base.addClass('ngy2_container');
10204
10205 // RTL or LTR
10206 // var sRTL='';
10207 // if( G.O.RTL ) {
10208 // sRTL = 'style="text-align:right;direction:rtl;"';
10209 // }
10210
10211 // theme
10212 G.$E.base.addClass(G.O.theme)
10213 // gallery color scheme
10214 SetGalleryTheme();
10215
10216 // Hide icons (thumbnails and breadcrumb)
10217 if( G.O.thumbnailLabel.get('hideIcons') ) {
10218 G.O.icons.thumbnailAlbum = '';
10219 G.O.icons.thumbnailImage = '';
10220 }
10221
10222 // Navigation bar
10223 var styleNavigation="";
10224 if( G.O.navigationFontSize != undefined && G.O.navigationFontSize != '' ) {
10225 styleNavigation=' style="font-size:'+G.O.navigationFontSize+';"';
10226 }
10227 G.$E.conNavigationBar = jQuery('<div class="nGY2Navigationbar" '+styleNavigation+'></div>').appendTo(G.$E.base);
10228
10229 // pre-loader
10230 G.$E.conLoadingB = jQuery('<div class="nanoGalleryLBarOff"><div></div><div></div><div></div><div></div><div></div></div>').appendTo(G.$E.base);
10231
10232 // gallery
10233 G.$E.conTnParent = jQuery('<div class="nGY2Gallery"></div>').appendTo( G.$E.base );
10234 G.$E.conTn = jQuery('<div class="nGY2GallerySub"></div>').appendTo( G.$E.conTnParent );
10235
10236 // configure gallery
10237 switch( G.O.thumbnailAlignment ) {
10238 case 'left':
10239 G.$E.conTnParent.css({'text-align':'left'});
10240 // G.$E.conNavBCon.css({'margin-left':0 });
10241 break;
10242 case 'right':
10243 G.$E.conTnParent.css({'text-align':'right'});
10244 // G.$E.conNavBCon.css({ 'margin-right':0});
10245 break;
10246 }
10247
10248 // apply galleryBuildInit2 css settings to the gallery
10249 if( G.O.galleryBuildInit2 !== undefined ) {
10250 var t1=G.O.galleryBuildInit2.split('|');
10251 for( var i=0; i<t1.length; i++ ) {
10252 var o1=t1[i].split('_');
10253 if( o1.length == 2 ) {
10254 G.$E.conTn.css(o1[0], o1[1]);
10255 }
10256 }
10257 }
10258
10259 // configure gallery depending on some thumbnail hover effects
10260 var effects=G.tn.hoverEffects.std.concat(G.tn.hoverEffects.level1);
10261 for( var j=0; j<effects.length; j++) {
10262 switch( effects[j].type ) {
10263 case 'scale':
10264 case 'rotateZ':
10265 case 'rotateX':
10266 case 'rotateY':
10267 case 'translateX':
10268 case 'translateY':
10269 // handle some special cases
10270 if( effects[j].element == '.nGY2GThumbnail' ) {
10271 // allow thumbnail upscale over the gallery's aera
10272 G.$E.base.css('overflow', 'visible');
10273 G.$E.base.find('.nGY2GallerySub').css('overflow', 'visible');
10274 G.$E.conTnParent.css('overflow', 'visible');
10275 }
10276 break;
10277 }
10278 }
10279
10280 // Gallery bottom container
10281 G.$E.conTnBottom = jQuery('<div class="nGY2GalleryBottom" '+styleNavigation+'></div>').appendTo( G.$E.conTnParent );
10282
10283 // portable edition
10284 if( G.O.portable ) {
10285 // http://www.picresize.com/
10286 // http://base64encode.net/base64-image-encoder
10287 // var logo='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB8AAAAWCAYAAAA4oUfxAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QA/wD/AP+gvaeTAAAAB3RJTUUH4QMPBwY6mxZgsAAABTFJREFUSMe1ll9oVGcaxn/fd86ZSWbSkEBMiWNdTTfRxiVbXFiU1bjKGqNexlURKys0tHqXpQZ64Sq4FxKqFy4qFSm9kA1FHNhFISgJqFCd6lL/YC7M3jhrJv5JmGSSMzPnzDnfuxdpZtP4b1vaF154P3gPD+/zPC/nVSKiAQOsBj7O5XK/nZiYeEtELH6iUEqFNTU1U9XV1d8AnwNfA1qJCMCfHz169NcjR45UXL16VWWzWQnD0PxU4JZl6draWtXW1iYHDx4sLlmy5C/AZwRB0JVOpyWRSHhACMjPmOHChQuL6XRagiDoUiIyumvXrpq+vr6obduqs7OTjRvbsbSFUgqUgKjyFG5+mlKpVH6LCMYYRAQRQSmF1hqtNd+xijGGVCpFMpkkCALZuXOn19fXN6Gmp6dNc3NzMDo66nR2dnL+/Hm+Ov933PwUAPHKagqei4gBFNs7dxGPx38U/du2bSOZTNLQ0FB6+PChbWez2WI+n3dEhI3tf+Det0N8de0Imz9YQWHa48u/3afjgxbqEpUM/es/uF8W+fijffi+TywWQ0S4fv06t2/fJpfLsXjxYtauXUtTUxNBECAihGFIJBJh1apVXLhwgXw+r7LZbNGeYU7MLD1BEPCLxkWs+HUT+SmPJY0TvPerd6l/J05YcLCGHWzbxrZtHjx4wP79+7l27dr3Jqyurqarq4ujR49i2zYAWmvCMJyVygCiZ7dh9kOtNb5XopD3KBQ8fL9EseBRyHsUCz6zS3Dnzh3WrVtXBq6oqGDBggUA5HI5jh07xo4dOzDmf0ujVBlGAWjmhTGC41hEow6RiI3j2DgRh0jUxonYWJaFGGHPnj2Mj49jWRYHDhzg7t27DA0NMTAwwOrVqwFIJpOcOHECx3Fe6oEXwG3bYux5ltHHz3mSGePpk+c8yczUI+knVFVVcePmDe7fvw9AT08Pvb29NDc3U1dXx4YNG7h8+TItLS1orTl58iT5fL68Ga8En55yWb6iifff/iPD/0iQGfglG3/zJ6a+beHf/3yH6Mjv+P269Vy5cgWlFDU1NXR3dxOGYdlcnudRVVXFvn37MMaQTqcZHh5+Kbg99zHjSodPuj997cqMjY0hItTW1hKPx9FalzW1LIswDFm0aBEAQRDguu6bJ581hOd5GBNiTEgYhuXa8z1EhIaGBgAymQzpdBqlFKVSiTCc6bcsi5s3bwJQWVlJfX39fMO9XHMAy7LQeibn1o7toJSio6MDAN/36e7uxvd9IpEIlmURjUZJpVKcOXMGpRStra0sXbr0peDfo30+LS+4U2uMMaxcuZLdu3dz7tw5+vv7aWtrY+/evdTX13Pr1i1OnTrF5OQkAIcPH8ayrNeCvx51njTGGE6fPk0mk2FwcJBUKkUqlXqh9/jx42zatKnMzJzhBEArpZT+zjGWZSEiBEHwypzVtbKykosXL3Lo0CEaGxvLpovFYqxZs4ZLly6VJQnDEBEpM6C11kopheu6JpFI+Fpr2bJli/zYGBkZkeHhYZmcnHxlz9atW0VrLYlEwndd19ixWOzx5s2b3z579qzp7+/X7e3ttLa2Yox5QaP5MfenEY1G0VoTBAHFYhFjTJlJrTX37t1jYGAAY4zp6OiQWCz2mCAItj979kyWL1/uAwE/7zERLFu2zH/69KkEQbB99ozaOz4+fqy3t7d2cHAwdF1XKaXe6P7/16AiQjwel/Xr1+uenp6Jurq6T4Av1JwD8j3gQ2BVsVh8S72J8x8QIiIVFRVTQAo4CwwB+r93qCLI9wKZ8AAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNy0wMy0xNVQwNzowNjo1OC0wNDowMBNQsyUAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTctMDMtMTVUMDc6MDY6NTgtMDQ6MDBiDQuZAAAAAElFTkSuQmCC';
10288 var logo = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAYCAYAAACbU/80AAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QA/wD/AP+gvaeTAAAAB3RJTUUH4QgDBCAWVVC/hwAABRxJREFUSMetll9oVFcexz/nnDvJRBmSzWTrmD9uNGZsHta0/qFIFQTxRcnCBgTFNlX0YR8W+1AK9lGwCBJYgn0KKr5136S4gpUQTR4caJRslcxYWV3iaphQapJJppO5957z60Mmk4mN1q75wg/OPefc+/v9vt/fueenKEFEqICqsNWAVNiCA7XwaS0iZeejo6OIiCltdIBdJXMLOYp5/PjxsoTVS5nr0mYDJIE/lObeBhaYAn4oJbboAwBvBedHJicnPx8YGGh/8eJF1dvKoJSShoYGf//+/Zl4PP4l8M2yIEoSLErx6c2bN6W1tXVRglWzLVu2SCqVEhE5LiI457SIoEREW2udMaZtcnLy+2QyWZ3L5XRHR4f+4MNdoBUahUJhcWilmZ/NE4ZhOQHn3LIi1lqjtS6vjY6O8uTJE9vc3MyDBw+mYrHYn0Uk63me8gCtlHLA7uHh4bW5XC7oePddPTQ8xHffDjM/PYe3thqMws35iAcHPj5ENBp9Yxmy2Sw7d+40z549C+7du9ewb9++D6y13wDaK+kE0DAzMyNKKbXtvfd5EfzM+Ef/4C+8x23+wzPm+IhtfMf3/Ksuyl+7u9FaY63l+vXrpFIpCoUCmzdvpquri9bWVoIgQClFIpFg48aNPH/+XE9NTQkQLTGmvEXKRERprZWIEIYhQRjQbN6hmUb+tCaPNnM055v40f3If7XBGMPT8af0fNLD0NDQsozPnDlDb28vx44dIwxDRARrLSKCKmUbiUQQkWWnoLJ20UpjFYAjVA6rBJTFV5ZIJIIfBBw4eICxsTHq6uo4dOgQ8XicgYEB7t69y/Hjx4nH43R1dVHB8q+w4hlXSmGd5edwmjCco5DLkZ+aJvTnyIdTrFmzhn9+/TVjY2M0NTVx+/Zt+vv7OXfuHKlUip6eHgBOnz6N7/vlYl0JKzIw78/T+sdGbn6yjf5ZS2HtJgIP+mcC5kySI1uSXPjqAlprTp06RWdnJ8ViEaUUVVVVnD9/nqtXr5LJZHj48CFbt279fQEEYUisZi2fXel9bWU750gmkwRBgNYaz/Ow1lJfX088Hmd2dpZcLvdaBl4pgQChH4B1iHU4a8E6Qj9ARGhpaUFrzeDgIJFIBGMM1lqMMWQyGSYmJohEIqxfv/7314CIoADtGTAaZTTaLI2VUhw+fBjnHBcvXuTy5cs45/A8j3Q6zcmTJ/F9n71799LW1rbgSOs3D+B1lBljcM7R3d3N0aNHKRQKnDhxgs7OTnbt2sX27dsZGRkhHo/T19e3+Kt/fQ1YawFwzolSCs/zUEqVtX1VcJcuXSKRSNDf3086nS6v79mzh76+Pjo6OigWi1RXV2OMWZC29PL8/PxSAL7vE41Gf4rFYkpEePToEb7vU1VVxW+ht7eXs2fPcv/+fQqFAps2baKlpaW8Xl1dTS6XY3x8HBFxtbW1BiiW4hAlInp8fNxt2LChPZvN/ru9vT2Sz+e93bt3qx07diwrzJWYcM5RU1NDNBots5bP53HOlS+kO3fuMDIy4hKJhKTT6ena2tqtxWJxoqamRr98HX9x7do1qaurExYaiXCVzK5bt04GBwdFRP728nVcWZAO+Hsmk/nsxo0bTTMzM5FXHZ83hYhQX1/vHzx48H9tbW1ngSsVvpYCmJ2dJRaLKRbapjpgOxB7K+9LmAbuAnOAnpiYcI2NjUsRLlo2myUMQ1M5t5rmnDO3bt1aNlfmd4W2XL/0/H8pUDF2rNCW/wLRuCkxx8V6wgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNy0wOC0wM1QwNDozMjoyMi0wNDowMO7mdkwAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTctMDgtMDNUMDQ6MzI6MjItMDQ6MDCfu87wAAAAAElFTkSuQmCC';
10289 var st = "font-weight:bold !important;color: #FF0075 !important;font-size: 14px !important;text-transform: lowercase !important;cursor:pointer !important;text-align: center !important;Text-Shadow: #000000 1px 0px 0px, #000000 1px 1px 0px, #000000 1px -1px 0px, #000000 -1px 1px 0px, #000000 -1px 0px 0px, #000000 -1px -1px 0px, #000000 0px 1px 0px, #000000 0px -1px 0px !important;";
10290 G.$E.ngy2i=jQuery('<div class="nGY2PortInfo"><a href="http://nano.gallery" target="_blank" title="nanogallery2 | easy photo gallery for your website" style="' + st + '"><img src="' + logo + '" style="height:32px !important;width:initial !important;box-shadow: none !important;vertical-align: middle !important;"/> &nbsp; nanogallery2</a></div>').appendTo(G.$E.base);
10291
10292 G.$E.ngy2i.find('a').on({
10293 mouseenter: function () {
10294 jQuery(this).attr('style', st);
10295 },
10296 mouseleave: function () {
10297 jQuery(this).attr('style', st);
10298 }
10299 });
10300 }
10301 }
10302
10303 // Error console
10304 G.$E.conConsole = jQuery('<div class="nGY2ConsoleParent"></div>').appendTo(G.$E.base);
10305
10306 // i18n translations
10307 i18n();
10308
10309 if( !G.O.lightboxStandalone ) {
10310 // cache some thumbnails data (sizes, styles...)
10311 ThumbnailDefCaches();
10312
10313 // do special settings depending for some options
10314 // thumbnail display transition
10315 switch( G.tn.opt.Get('displayTransition') ) {
10316 case 'SCALEDOWN':
10317 case 'RANDOMSCALE':
10318 default:
10319 G.$E.base.css('overflow', 'visible');
10320 G.$E.conTnParent.css('overflow', 'visible');
10321 G.$E.conTn.css('overflow', 'visible');
10322 break;
10323 }
10324 }
10325
10326 }
10327
10328 function TriggerCustomEvent ( eventName ) {
10329 // G.$E.base.trigger('pageChanged.nanogallery2', new Event('pageChanged.nanogallery2'));
10330 var eN = eventName + '.nanogallery2';
10331 var event=null;
10332 try {
10333 event = new Event( eN );
10334 } catch(e) {
10335 event = document.createEvent('Event');
10336 event.initEvent(eN, false, false);
10337 }
10338 G.$E.base.trigger(eN, event);
10339 }
10340
10341
10342 /** @function SetGlobalEvents */
10343 function SetGlobalEvents() {
10344 // GLOBAL EVENT MANAGEMENT
10345
10346 if( !G.O.lightboxStandalone ) {
10347 G.$E.conTnParent.on({
10348 mouseenter: GalleryMouseEnter,
10349 mouseleave: GalleryMouseLeave
10350 }, ".nGY2GThumbnail"); //pass the element as an argument to .on
10351
10352 // G.GOM.hammertime = new NGHammer(G.$E.conTn[0], { touchAction: 'none' });
10353 G.GOM.hammertime = new NGHammer( G.$E.conTn[0] );
10354 // G.GOM.hammertime.domEvents = true;
10355
10356
10357 // PAN on gallery (pagination)
10358 G.GOM.hammertime.on('pan', function(ev) {
10359 if( !G.VOM.viewerDisplayed ) {
10360 if( G.O.paginationSwipe && G.layout.support.rows && G.galleryDisplayMode.Get() == 'PAGINATION' ) {
10361 if( Math.abs(ev.deltaY) > G.GOM.panThreshold ) {
10362 G.GOM.panYOnly = true;
10363 }
10364 if( !G.GOM.panYOnly ) {
10365 G.$E.conTn.css( G.CSStransformName , 'translate('+(ev.deltaX)+'px,0px)');
10366 }
10367 }
10368 }
10369 });
10370 G.GOM.hammertime.on('panend', function(ev) {
10371 if( !G.VOM.viewerDisplayed ) {
10372 if( G.O.paginationSwipe && G.layout.support.rows && G.galleryDisplayMode.Get() == 'PAGINATION' ) {
10373 if( !G.GOM.panYOnly ) {
10374 if( ev.deltaX > 50 ) {
10375 paginationPreviousPage();
10376 return;
10377 }
10378 if( ev.deltaX < -50 ) {
10379 paginationNextPage();
10380 return;
10381 }
10382 }
10383 G.GOM.panYOnly = false;
10384 G.$E.conTn.css( G.CSStransformName , 'translate(0px,0px)');
10385 // pX=0;
10386 }
10387 }
10388 });
10389 // tap on gallery
10390 G.GOM.hammertime.on('tap', function(ev) {
10391 if( !G.VOM.viewerDisplayed ) {
10392 ev.srcEvent.stopPropagation();
10393 ev.srcEvent.preventDefault(); // cancel mouseenter event
10394
10395 if( ev.pointerType == 'mouse') {
10396 if( GalleryClicked(ev.srcEvent) == 'exit' ) { return; }
10397 }
10398 else {
10399 var r = GalleryEventRetrieveElementl(ev.srcEvent, false);
10400 if( r.GOMidx == -1 ) { return; }
10401 if( r.action != 'NONE' && r.action != 'OPEN' ) {
10402 // toolbar touched --> execute action
10403 GalleryClicked(ev.srcEvent);
10404 return;
10405 }
10406
10407 if( G.GOM.slider.hostIdx == r.GOMidx ) {
10408 // touch on thumbnail slider -> open immediately
10409 ThumbnailHoverOutAll();
10410 ThumbnailOpen(G.GOM.items[G.GOM.slider.currentIdx].thumbnailIdx, true);
10411 return;
10412 }
10413
10414 if( (G.GOM.curNavLevel == 'l1' && G.O.touchAnimationL1 == false) || (G.GOM.curNavLevel == 'lN' && G.O.touchAnimation == false) ) {
10415 // open on single touch (no hover animation)
10416 ThumbnailOpen(G.GOM.items[r.GOMidx].thumbnailIdx, true);
10417 return;
10418 }
10419
10420 if( G.O.touchAutoOpenDelay > 0 ) {
10421 // open on single touch after end of hover animation (=defined delay)
10422 ThumbnailHoverOutAll();
10423 ThumbnailHover( r.GOMidx );
10424 window.clearInterval( G.touchAutoOpenDelayTimerID );
10425 G.touchAutoOpenDelayTimerID = window.setInterval(function(){
10426 window.clearInterval( G.touchAutoOpenDelayTimerID );
10427 ThumbnailOpen( G.GOM.items[r.GOMidx].thumbnailIdx, true );
10428 }, G.O.touchAutoOpenDelay );
10429 }
10430 else {
10431 // two touch scenario
10432 if( !G.I[G.GOM.items[r.GOMidx].thumbnailIdx].hovered ) {
10433 ThumbnailHoverOutAll();
10434 ThumbnailHover(r.GOMidx);
10435 }
10436 else {
10437 // second touch
10438 ThumbnailOpen(G.GOM.items[r.GOMidx].thumbnailIdx, true);
10439 }
10440 }
10441 }
10442 }
10443 });
10444
10445 // browser location hash management
10446 if( G.O.locationHash ) {
10447 // jQuery(window).bind( 'hashchange', function() {
10448 // ProcessLocationHash();
10449 // });
10450 jQuery(window).on('hashchange.nanogallery2.' + G.baseEltID, function() {ProcessLocationHash();} );
10451 }
10452 }
10453
10454 // Page resize / orientation change
10455 jQuery(window).on('resize.nanogallery2.' + G.baseEltID + ' orientationChange.nanogallery2.' + G.baseEltID, debounce( ResizeWindowEvent, G.O.eventsDebounceDelay, false) );
10456
10457 // Event page scrolled
10458 jQuery(window).on('scroll.nanogallery2.' + G.baseEltID, debounce( OnScrollEvent, G.O.eventsDebounceDelay, false) );
10459
10460 if( !G.O.lightboxStandalone ) {
10461 // Scroll event on first scrollable parent element
10462 G.$E.scrollableParent = getScrollableParent( G.$E.base[0] );
10463 var sp = getScrollableParent( G.$E.base[0] );
10464 if( sp !== null ) {
10465 G.$E.scrollableParent = jQuery( sp );
10466 G.$E.scrollableParent.on('scroll.nanogallery2.' + G.baseEltID, debounce( OnScrollEvent, G.O.eventsDebounceDelay, false) );
10467 }
10468 }
10469
10470 // lightbox: hide tools/gallery after defined delay
10471 G.VOM.toolsHide = debounce( ViewerToolsHide, G.O.viewerHideToolsDelay, false );
10472
10473 // Keyboard management
10474 jQuery(document).keyup(function(e) {
10475 if( G.popup.isDisplayed ) {
10476 switch( e.keyCode) {
10477 case 27: // Esc key
10478 G.popup.close();
10479 break;
10480 }
10481 }
10482 else {
10483 if( G.VOM.viewerDisplayed ) {
10484 ViewerToolsUnHide();
10485 switch( e.keyCode) {
10486 case 27: // Escape key
10487 case 40: // DOWN
10488 case 38: // UP
10489 LightboxClose();
10490 break;
10491 case 32: // SPACE
10492 case 13: // ENTER
10493 SlideshowToggle();
10494 break;
10495 case 39: // RIGHT
10496 case 33: // PAGE UP
10497 DisplayNextMedia();
10498 break;
10499 case 37: // LEFT
10500 case 34: // PAGE DOWN
10501 DisplayPreviousMedia();
10502 break;
10503 case 35: // END
10504 case 36: // BEGIN
10505 }
10506 }
10507 }
10508 });
10509
10510 // mouse wheel to zoom in/out the image displayed in the internal lightbox
10511 jQuery(window).bind('mousewheel wheel', function(e){
10512
10513 if( G.VOM.viewerDisplayed && G.VOM.content.current.NGY2Item().mediaKind == 'img' ) {
10514
10515 var deltaY = 0;
10516 e.preventDefault();
10517
10518 if( ViewerZoomStart() ) {
10519 if (e.originalEvent.deltaY) { // FireFox 17+ (IE9+, Chrome 31+?)
10520 deltaY = e.originalEvent.deltaY;
10521 } else if (e.originalEvent.wheelDelta) {
10522 deltaY = -e.originalEvent.wheelDelta;
10523 }
10524 ViewerZoomIn( deltaY <= 0 ? true : false );
10525 }
10526 }
10527 });
10528
10529 // mouse move -> unhide lightbox toolbars
10530 jQuery(window).bind('mousemove', function(e){
10531 if( G.VOM.viewerDisplayed ) {
10532 if( G.VOM.toolbarsDisplayed == false ) {
10533 G.VOM.singletapTime = new Date().getTime(); // to avoid conflict with SINGLETAP event
10534 debounce( ViewerToolsUnHide, 100, false )();
10535 }
10536 }
10537 });
10538
10539 // fullscreen mode on/off --> internal lightbox
10540 if( ngscreenfull.enabled ) {
10541 // ngscreenfull.onchange(() => {
10542 ngscreenfull.onchange( function() {
10543 if( G.VOM.viewerDisplayed ) {
10544 if( ngscreenfull.isFullscreen ) {
10545 G.VOM.viewerIsFullscreen=true;
10546 G.VOM.$viewer.find('.fullscreenButton').html(G.O.icons.viewerFullscreenOff);
10547 }
10548 else {
10549 G.VOM.viewerIsFullscreen=false;
10550 G.VOM.$viewer.find('.fullscreenButton').html(G.O.icons.viewerFullscreenOn);
10551 }
10552 }
10553 });
10554 }
10555
10556 }
10557
10558
10559 //----- Manage browser location hash (deep linking and browser back/forward)
10560 function ProcessLocationHash() {
10561
10562 // standard use case -> location hash processing
10563 if( !G.O.locationHash ) { return false; }
10564
10565 var curGal = '#nanogallery/' + G.baseEltID + '/',
10566 newLocationHash = location.hash;
10567 if( G.O.debugMode ) {
10568 console.log('------------------------ PROCESS LOCATION HASH');
10569 console.log('newLocationHash1: ' +newLocationHash);
10570 console.log('G.locationHashLastUsed: ' +G.locationHashLastUsed);
10571 }
10572
10573 if( newLocationHash == '' ) {
10574 // if( G.GOM.lastDisplayedIdx != -1 ) {
10575 if( G.locationHashLastUsed !== '' ) {
10576 // back button and no hash --> display first album
10577 if( G.O.debugMode ) { console.log('display root album' ); }
10578 G.locationHashLastUsed = '';
10579 if( G.O.debugMode ) { console.log('new3 G.locationHashLastUsed: ' + G.locationHashLastUsed); }
10580 DisplayAlbum('', '0');
10581 return true;
10582 }
10583 }
10584
10585 if( newLocationHash == G.locationHashLastUsed ) { return; }
10586
10587 if( newLocationHash.indexOf(curGal) == 0 ) {
10588 // item IDs detected
10589 var IDs=parseIDs( newLocationHash.substring(curGal.length) );
10590 if( IDs.imageID != '0' ) {
10591 if( G.O.debugMode ) { console.log('display image: ' + IDs.albumID +'-'+ IDs.imageID ); }
10592 DisplayPhoto( IDs.imageID, IDs.albumID );
10593 return true;
10594 }
10595 else {
10596 if( G.O.debugMode ) { console.log('display album: ' + IDs.albumID ); }
10597 DisplayAlbum( '-1', IDs.albumID );
10598 return true;
10599 }
10600 }
10601
10602 return false;
10603 }
10604
10605 //---- Set a new browser location hash
10606 function SetLocationHash(albumID, imageID ) {
10607 if( !G.O.locationHash || G.O.lightboxStandalone ) { return false; }
10608
10609 if( G.O.debugMode ) {
10610 console.log('------------------------ SET LOCATION HASH');
10611 }
10612
10613 if( imageID == '' && (albumID == '-1' || albumID == '0' || G.O.album == albumID ) ) {
10614 // root album level --> do not set top.location.hash if not already set
10615 if( location.hash != '' ) {
10616 // try to clear the hash if set
10617 if ("pushState" in history) {
10618 history.pushState("", document.title, window.location.pathname + window.location.search);
10619 }
10620 else {
10621 location.hash='';
10622 }
10623 }
10624 G.locationHashLastUsed='';
10625 if( G.O.debugMode ) { console.log('new2 G.locationHashLastUsed: '+G.locationHashLastUsed); }
10626 return;
10627 }
10628
10629 var newLocationHash='#'+'nanogallery/'+G.baseEltID+'/'+ albumID;
10630 if( imageID != '' ) {
10631 newLocationHash+='/'+imageID;
10632 }
10633
10634 var lH=location.hash;
10635 if( G.O.debugMode ) {
10636 console.log('newLocationHash2: '+newLocationHash);
10637 console.log('location.hash: '+lH);
10638 }
10639
10640 G.locationHashLastUsed=newLocationHash;
10641 if( G.O.debugMode ) { console.log('new G.locationHashLastUsed: '+G.locationHashLastUsed); }
10642
10643 if( lH == '' || lH != newLocationHash ) {
10644 // G.locationHashLastUsed='#'+newLocationHash;
10645 try {
10646 top.location.hash=newLocationHash;
10647 }
10648 catch(e) {
10649 // location hash is not supported by current browser --> disable the option
10650 G.O.locationHash=false;
10651 }
10652 }
10653 }
10654
10655
10656 // WINDOW RESIZE EVENT
10657 function ResizeWindowEvent() {
10658 CacheViewport();
10659
10660 var l = G.GOM.curNavLevel;
10661 var w = G.GOM.curWidth;
10662
10663 if( G.VOM.viewerDisplayed ) {
10664 // lightbox
10665 ResizeLightbox();
10666 G.VOM.gallery.Resize();
10667 }
10668 else {
10669 // gallery
10670 if( G.galleryResizeEventEnabled ) {
10671 var nw = RetrieveCurWidth();
10672
10673 if( G.GOM.albumIdx != -1 ) {
10674
10675 // check if the gallery needs to be rendered again because the width changed
10676
10677 var s = G.tn.settings;
10678 if( G.layout.engine == "MOSAIC") {
10679 // Mosaic layout
10680 if( JSON.stringify(s.mosaic[l][w]) !== JSON.stringify(s.mosaic[l][nw]) ) {
10681 // mosaic definition changed
10682 G.GOM.curWidth = nw;
10683 G.GOM.pagination.currentPage = 0;
10684 GalleryRender( G.GOM.albumIdx );
10685 return;
10686 }
10687 }
10688 else {
10689 // other layouts
10690 if( s.height[l][w] != s.height[l][nw] || s.width[l][w] != s.width[l][nw] || s.gutterHeight[l][w] != s.gutterHeight[l][nw] || s.gutterWidth[l][w] != s.gutterWidth[l][nw] ) {
10691 // thumbnail size / gutter size changed --> render the gallery with the new values
10692 G.GOM.curWidth = nw;
10693 //G.layout.SetEngine();
10694 G.GOM.pagination.currentPage = 0;
10695 GalleryRender( G.GOM.albumIdx );
10696 return;
10697 }
10698 }
10699 // return;
10700 }
10701 // else {
10702 GalleryResize();
10703 // }
10704 }
10705 }
10706 }
10707
10708
10709
10710 // SCROLL EVENT -> on WINDOW or SCROLLABLE PARENT CONTAINER
10711 function OnScrollEvent() {
10712 if( !G.VOM.viewerDisplayed ) {
10713 GalleryResizeOnScrollEvent();
10714 }
10715 }
10716
10717 // the gallery may currently be refreshed, so ensure that at the end of the refresh, the gallery is refreshed again because the page may have been scrolled in the meantime
10718 function GalleryResizeOnScrollEvent() {
10719 if( G.galleryResizeEventEnabled == false) {
10720 window.setTimeout(GalleryResizeOnScrollEvent, 10); // check again in 10ms
10721 } else {
10722 GalleryResize();
10723 }
10724 }
10725
10726
10727
10728 // I18N : define text translations
10729 function i18n() {
10730
10731 // browser language
10732 G.i18nLang = (navigator.language || navigator.userLanguage).toUpperCase();
10733 if( G.i18nLang === 'UNDEFINED') { G.i18nLang=''; }
10734
10735 var llang=-('_'+G.i18nLang).length;
10736
10737 if( toType(G.O.i18n) == 'object' ){
10738
10739 for( var key in G.O.i18n ) {
10740 //var value = G.O.i18n[key];
10741 var s=key.substr(llang);
10742 if( s == ('_'+G.i18nLang) ) {
10743 G.i18nTranslations[key.substr(0,key.length-s.length)]=G.O.i18n[key];
10744 }
10745 else {
10746 G.i18nTranslations[key]=G.O.i18n[key];
10747 }
10748 }
10749 }
10750 }
10751
10752 function GetI18nItem( item, property ) {
10753 var s='';
10754 if( G.i18nLang != '' ) {
10755 if( item[property+'_'+G.i18nLang] !== undefined && item[property+'_'+G.i18nLang].length>0 ) {
10756 s=item[property+'_'+G.i18nLang];
10757 return s;
10758 }
10759 }
10760 s=item[property];
10761 return s;
10762 }
10763
10764
10765 function RetrieveCurWidth() {
10766 var vpW = G.GOM.cache.viewport.w;
10767
10768 if( G.O.breakpointSizeSM > 0 && vpW < G.O.breakpointSizeSM) { return 'xs'; }
10769 if( G.O.breakpointSizeME > 0 && vpW < G.O.breakpointSizeME) { return 'sm'; }
10770 if( G.O.breakpointSizeLA > 0 && vpW < G.O.breakpointSizeLA) { return 'me'; }
10771 if( G.O.breakpointSizeXL > 0 && vpW < G.O.breakpointSizeXL) { return 'la'; }
10772
10773 return 'xl';
10774 }
10775
10776
10777 /** @function browserNotification */
10778 function browserNotification() {
10779 var m = 'Your browser version is not supported anymore. The image gallery cannot be displayed. <br><br>Please update to a more recent one. Download:<br>';
10780 m += '&nbsp;&nbsp;&nbsp; <a href="http://www.google.com/chrome/?hl=en-US)">Chrome</a><br>';
10781 m += '&nbsp;&nbsp;&nbsp; <a href="http://www.mozilla.com/firefox/)">Firefox</a><br>';
10782 m += '&nbsp;&nbsp;&nbsp; <a href="http://www.microsoft.com/windows/internet-explorer/default.aspx">Internet Explorer</a><br>';
10783 m += '&nbsp;&nbsp;&nbsp; <a href="http://www.apple.com/safari/download/">Safari</a>';
10784 NanoAlert(G, m, false);
10785 }
10786
10787 // Original author : John Hrvatin, Lead Program Manager, Internet Explorer - http://blogs.msdn.com/b/ie/archive/2011/10/28/a-best-practice-for-programming-with-vendor-prefixes.aspx
10788 function FirstSupportedPropertyName(prefixedPropertyNames) {
10789 var tempDiv = document.createElement("div");
10790 for (var i = 0; i < prefixedPropertyNames.length; ++i) {
10791 if (typeof tempDiv.style[prefixedPropertyNames[i]] != 'undefined')
10792 return prefixedPropertyNames[i];
10793 }
10794 return null;
10795 }
10796
10797
10798
10799
10800 }
10801
10802
10803
10804//##########################################################################################################################
10805//## imagesLoaded ##########################################################################################################
10806//##########################################################################################################################
10807
10808// external module EMBEDED in nanogallery
10809// NGY BUILD:
10810// replace "imagesLoaded" with "ngimagesLoaded"
10811// replace "ImagesLoaded" with "ngImagesLoaded"
10812// replace "EvEmitter" with "ngEvEmitter"
10813// replace "var $ = window.jQuery" with "var $ = jQuery;"
10814// 2x (global.ngEvEmitter and window.ngimagesLoaded = f...)ignore package manager and set browser global
10815
10816
10817
10818/*!
10819 * imagesLoaded PACKAGED v4.1.1
10820 * JavaScript is all like "You images are done yet or what?"
10821 * MIT License
10822 */
10823
10824/**
10825 * EvEmitter v1.0.3
10826 * Lil' event emitter
10827 * MIT License
10828 */
10829
10830
10831/* jshint unused: true, undef: true, strict: true */
10832
10833( function( global, factory ) {
10834 // universal module definition
10835 /* jshint strict: false */ /* globals define, module, window */
10836// if ( typeof define == 'function' && define.amd ) {
10837 // AMD - RequireJS
10838// define( 'ev-emitter/ev-emitter',factory );
10839// } else if ( typeof module == 'object' && module.exports ) {
10840 // CommonJS - Browserify, Webpack
10841// module.exports = factory();
10842// } else {
10843 // Browser globals
10844 global.ngEvEmitter = factory();
10845// }
10846
10847}( typeof window != 'undefined' ? window : this, function() {
10848
10849
10850
10851function ngEvEmitter() {}
10852
10853var proto = ngEvEmitter.prototype;
10854
10855proto.on = function( eventName, listener ) {
10856 if ( !eventName || !listener ) {
10857 return;
10858 }
10859 // set events hash
10860 var events = this._events = this._events || {};
10861 // set listeners array
10862 var listeners = events[ eventName ] = events[ eventName ] || [];
10863 // only add once
10864 if ( listeners.indexOf( listener ) == -1 ) {
10865 listeners.push( listener );
10866 }
10867
10868 return this;
10869};
10870
10871proto.once = function( eventName, listener ) {
10872 if ( !eventName || !listener ) {
10873 return;
10874 }
10875 // add event
10876 this.on( eventName, listener );
10877 // set once flag
10878 // set onceEvents hash
10879 var onceEvents = this._onceEvents = this._onceEvents || {};
10880 // set onceListeners object
10881 var onceListeners = onceEvents[ eventName ] = onceEvents[ eventName ] || {};
10882 // set flag
10883 onceListeners[ listener ] = true;
10884
10885 return this;
10886};
10887
10888proto.off = function( eventName, listener ) {
10889 var listeners = this._events && this._events[ eventName ];
10890 if ( !listeners || !listeners.length ) {
10891 return;
10892 }
10893 var index = listeners.indexOf( listener );
10894 if ( index != -1 ) {
10895 listeners.splice( index, 1 );
10896 }
10897
10898 return this;
10899};
10900
10901proto.emitEvent = function( eventName, args ) {
10902 var listeners = this._events && this._events[ eventName ];
10903 if ( !listeners || !listeners.length ) {
10904 return;
10905 }
10906 var i = 0;
10907 var listener = listeners[i];
10908 args = args || [];
10909 // once stuff
10910 var onceListeners = this._onceEvents && this._onceEvents[ eventName ];
10911
10912 while ( listener ) {
10913 var isOnce = onceListeners && onceListeners[ listener ];
10914 if ( isOnce ) {
10915 // remove listener
10916 // remove before trigger to prevent recursion
10917 this.off( eventName, listener );
10918 // unset once flag
10919 delete onceListeners[ listener ];
10920 }
10921 // trigger listener
10922 listener.apply( this, args );
10923 // get next listener
10924 i += isOnce ? 0 : 1;
10925 listener = listeners[i];
10926 }
10927
10928 return this;
10929};
10930
10931return ngEvEmitter;
10932
10933}));
10934
10935/*!
10936 * ngimagesLoaded v4.1.1
10937 * JavaScript is all like "You images are done yet or what?"
10938 * MIT License
10939 */
10940
10941( function( window, factory ) { 'use strict';
10942 // universal module definition
10943
10944 /*global define: false, module: false, require: false */
10945
10946// if ( typeof define == 'function' && define.amd ) {
10947 // AMD
10948// define( [
10949// 'ev-emitter/ev-emitter'
10950// ], function( ngEvEmitter ) {
10951// return factory( window, ngEvEmitter );
10952// });
10953// } else if ( typeof module == 'object' && module.exports ) {
10954 // CommonJS
10955// module.exports = factory(
10956// window,
10957// require('ev-emitter')
10958// );
10959// } else {
10960 // browser global
10961 window.ngimagesLoaded = factory(
10962 window,
10963 window.ngEvEmitter
10964 );
10965 //}
10966
10967})( window,
10968
10969// -------------------------- factory -------------------------- //
10970
10971function factory( window, ngEvEmitter ) {
10972
10973
10974
10975// var $ = window.jQuery;
10976var $ = jQuery;
10977var console = window.console;
10978
10979// -------------------------- helpers -------------------------- //
10980
10981// extend objects
10982function extend( a, b ) {
10983 for ( var prop in b ) {
10984 a[ prop ] = b[ prop ];
10985 }
10986 return a;
10987}
10988
10989// turn element or nodeList into an array
10990function makeArray( obj ) {
10991 var ary = [];
10992 if ( Array.isArray( obj ) ) {
10993 // use object if already an array
10994 ary = obj;
10995 } else if ( typeof obj.length == 'number' ) {
10996 // convert nodeList to array
10997 for ( var i=0; i < obj.length; i++ ) {
10998 ary.push( obj[i] );
10999 }
11000 } else {
11001 // array of single index
11002 ary.push( obj );
11003 }
11004 return ary;
11005}
11006
11007// -------------------------- ngimagesLoaded -------------------------- //
11008
11009/**
11010 * @param {Array, Element, NodeList, String} elem
11011 * @param {Object or Function} options - if function, use as callback
11012 * @param {Function} onAlways - callback function
11013 */
11014function ngImagesLoaded( elem, options, onAlways ) {
11015 // coerce ngImagesLoaded() without new, to be new ngImagesLoaded()
11016 if ( !( this instanceof ngImagesLoaded ) ) {
11017 return new ngImagesLoaded( elem, options, onAlways );
11018 }
11019 // use elem as selector string
11020 if ( typeof elem == 'string' ) {
11021 elem = document.querySelectorAll( elem );
11022 }
11023
11024 this.elements = makeArray( elem );
11025 this.options = extend( {}, this.options );
11026
11027 if ( typeof options == 'function' ) {
11028 onAlways = options;
11029 } else {
11030 extend( this.options, options );
11031 }
11032
11033 if ( onAlways ) {
11034 this.on( 'always', onAlways );
11035 }
11036
11037 this.getImages();
11038
11039 if ( $ ) {
11040 // add jQuery Deferred object
11041 this.jqDeferred = new $.Deferred();
11042 }
11043
11044 // HACK check async to allow time to bind listeners
11045 setTimeout( function() {
11046 this.check();
11047 }.bind( this ));
11048}
11049
11050ngImagesLoaded.prototype = Object.create( ngEvEmitter.prototype );
11051
11052ngImagesLoaded.prototype.options = {};
11053
11054ngImagesLoaded.prototype.getImages = function() {
11055 this.images = [];
11056
11057 // filter & find items if we have an item selector
11058 this.elements.forEach( this.addElementImages, this );
11059};
11060
11061/**
11062 * @param {Node} element
11063 */
11064ngImagesLoaded.prototype.addElementImages = function( elem ) {
11065 // filter siblings
11066 if ( elem.nodeName == 'IMG' ) {
11067 this.addImage( elem );
11068 }
11069 // get background image on element
11070 if ( this.options.background === true ) {
11071 this.addElementBackgroundImages( elem );
11072 }
11073
11074 // find children
11075 // no non-element nodes, #143
11076 var nodeType = elem.nodeType;
11077 if ( !nodeType || !elementNodeTypes[ nodeType ] ) {
11078 return;
11079 }
11080 var childImgs = elem.querySelectorAll('img');
11081 // concat childElems to filterFound array
11082 for ( var i=0; i < childImgs.length; i++ ) {
11083 var img = childImgs[i];
11084 this.addImage( img );
11085 }
11086
11087 // get child background images
11088 if ( typeof this.options.background == 'string' ) {
11089 var children = elem.querySelectorAll( this.options.background );
11090 for ( i=0; i < children.length; i++ ) {
11091 var child = children[i];
11092 this.addElementBackgroundImages( child );
11093 }
11094 }
11095};
11096
11097var elementNodeTypes = {
11098 1: true,
11099 9: true,
11100 11: true
11101};
11102
11103ngImagesLoaded.prototype.addElementBackgroundImages = function( elem ) {
11104 var style = getComputedStyle( elem );
11105 if ( !style ) {
11106 // Firefox returns null if in a hidden iframe https://bugzil.la/548397
11107 return;
11108 }
11109 // get url inside url("...")
11110 var reURL = /url\((['"])?(.*?)\1\)/gi;
11111 var matches = reURL.exec( style.backgroundImage );
11112 while ( matches !== null ) {
11113 var url = matches && matches[2];
11114 if ( url ) {
11115 this.addBackground( url, elem );
11116 }
11117 matches = reURL.exec( style.backgroundImage );
11118 }
11119};
11120
11121/**
11122 * @param {Image} img
11123 */
11124ngImagesLoaded.prototype.addImage = function( img ) {
11125 var loadingImage = new LoadingImage( img );
11126 this.images.push( loadingImage );
11127};
11128
11129ngImagesLoaded.prototype.addBackground = function( url, elem ) {
11130 var background = new Background( url, elem );
11131 this.images.push( background );
11132};
11133
11134ngImagesLoaded.prototype.check = function() {
11135 var _this = this;
11136 this.progressedCount = 0;
11137 this.hasAnyBroken = false;
11138 // complete if no images
11139 if ( !this.images.length ) {
11140 this.complete();
11141 return;
11142 }
11143
11144 function onProgress( image, elem, message ) {
11145 // HACK - Chrome triggers event before object properties have changed. #83
11146 setTimeout( function() {
11147 _this.progress( image, elem, message );
11148 });
11149 }
11150
11151 this.images.forEach( function( loadingImage ) {
11152 loadingImage.once( 'progress', onProgress );
11153 loadingImage.check();
11154 });
11155};
11156
11157ngImagesLoaded.prototype.progress = function( image, elem, message ) {
11158 this.progressedCount++;
11159 this.hasAnyBroken = this.hasAnyBroken || !image.isLoaded;
11160 // progress event
11161 this.emitEvent( 'progress', [ this, image, elem ] );
11162 if ( this.jqDeferred && this.jqDeferred.notify ) {
11163 this.jqDeferred.notify( this, image );
11164 }
11165 // check if completed
11166 if ( this.progressedCount == this.images.length ) {
11167 this.complete();
11168 }
11169
11170 if ( this.options.debug && console ) {
11171 console.log( 'progress: ' + message, image, elem );
11172 }
11173};
11174
11175ngImagesLoaded.prototype.complete = function() {
11176 var eventName = this.hasAnyBroken ? 'fail' : 'done';
11177 this.isComplete = true;
11178 this.emitEvent( eventName, [ this ] );
11179 this.emitEvent( 'always', [ this ] );
11180 if ( this.jqDeferred ) {
11181 var jqMethod = this.hasAnyBroken ? 'reject' : 'resolve';
11182 this.jqDeferred[ jqMethod ]( this );
11183 }
11184};
11185
11186// -------------------------- -------------------------- //
11187
11188function LoadingImage( img ) {
11189 this.img = img;
11190}
11191
11192LoadingImage.prototype = Object.create( ngEvEmitter.prototype );
11193
11194LoadingImage.prototype.check = function() {
11195 // If complete is true and browser supports natural sizes,
11196 // try to check for image status manually.
11197 var isComplete = this.getIsImageComplete();
11198 if ( isComplete ) {
11199 // report based on naturalWidth
11200 this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' );
11201 return;
11202 }
11203
11204 // If none of the checks above matched, simulate loading on detached element.
11205 this.proxyImage = new Image();
11206 this.proxyImage.addEventListener( 'load', this );
11207 this.proxyImage.addEventListener( 'error', this );
11208 // bind to image as well for Firefox. #191
11209 this.img.addEventListener( 'load', this );
11210 this.img.addEventListener( 'error', this );
11211 this.proxyImage.src = this.img.src;
11212};
11213
11214LoadingImage.prototype.getIsImageComplete = function() {
11215 return this.img.complete && this.img.naturalWidth !== undefined;
11216};
11217
11218LoadingImage.prototype.confirm = function( isLoaded, message ) {
11219 this.isLoaded = isLoaded;
11220 this.emitEvent( 'progress', [ this, this.img, message ] );
11221};
11222
11223// ----- events ----- //
11224
11225// trigger specified handler for event type
11226LoadingImage.prototype.handleEvent = function( event ) {
11227 var method = 'on' + event.type;
11228 if ( this[ method ] ) {
11229 this[ method ]( event );
11230 }
11231};
11232
11233LoadingImage.prototype.onload = function() {
11234 this.confirm( true, 'onload' );
11235 this.unbindEvents();
11236};
11237
11238LoadingImage.prototype.onerror = function() {
11239 this.confirm( false, 'onerror' );
11240 this.unbindEvents();
11241};
11242
11243LoadingImage.prototype.unbindEvents = function() {
11244 this.proxyImage.removeEventListener( 'load', this );
11245 this.proxyImage.removeEventListener( 'error', this );
11246 this.img.removeEventListener( 'load', this );
11247 this.img.removeEventListener( 'error', this );
11248};
11249
11250// -------------------------- Background -------------------------- //
11251
11252function Background( url, element ) {
11253 this.url = url;
11254 this.element = element;
11255 this.img = new Image();
11256}
11257
11258// inherit LoadingImage prototype
11259Background.prototype = Object.create( LoadingImage.prototype );
11260
11261Background.prototype.check = function() {
11262 this.img.addEventListener( 'load', this );
11263 this.img.addEventListener( 'error', this );
11264 this.img.src = this.url;
11265 // check if image is already complete
11266 var isComplete = this.getIsImageComplete();
11267 if ( isComplete ) {
11268 this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' );
11269 this.unbindEvents();
11270 }
11271};
11272
11273Background.prototype.unbindEvents = function() {
11274 this.img.removeEventListener( 'load', this );
11275 this.img.removeEventListener( 'error', this );
11276};
11277
11278Background.prototype.confirm = function( isLoaded, message ) {
11279 this.isLoaded = isLoaded;
11280 this.emitEvent( 'progress', [ this, this.element, message ] );
11281};
11282
11283// -------------------------- jQuery -------------------------- //
11284
11285ngImagesLoaded.makeJQueryPlugin = function( jQuery ) {
11286 jQuery = jQuery || window.jQuery;
11287 if ( !jQuery ) {
11288 return;
11289 }
11290 // set local variable
11291 $ = jQuery;
11292 // $().ngimagesLoaded()
11293 $.fn.ngimagesLoaded = function( options, callback ) {
11294 var instance = new ngImagesLoaded( this, options, callback );
11295 return instance.jqDeferred.promise( $(this) );
11296 };
11297};
11298// try making plugin
11299ngImagesLoaded.makeJQueryPlugin();
11300
11301// -------------------------- -------------------------- //
11302
11303return ngImagesLoaded;
11304
11305});
11306
11307
11308
11309//##########################################################################################################################
11310//## screenfull.js #########################################################################################################
11311//##########################################################################################################################
11312
11313// screenfull.js
11314// v4.0.1
11315// by sindresorhus - https://github.com/sindresorhus
11316// from: https://github.com/sindresorhus/screenfull.js
11317
11318// external module embeded in nanogallery
11319// NGY BUILD:
11320// replace "screenfull" with "ngscreenfull"
11321//
11322
11323(function () {
11324 'use strict';
11325
11326 var document = typeof window !== 'undefined' && typeof window.document !== 'undefined' ? window.document : {};
11327 var isCommonjs = typeof module !== 'undefined' && module.exports;
11328 var keyboardAllowed = typeof Element !== 'undefined' && 'ALLOW_KEYBOARD_INPUT' in Element;
11329
11330 var fn = (function () {
11331 var val;
11332
11333 var fnMap = [
11334 [
11335 'requestFullscreen',
11336 'exitFullscreen',
11337 'fullscreenElement',
11338 'fullscreenEnabled',
11339 'fullscreenchange',
11340 'fullscreenerror'
11341 ],
11342 // New WebKit
11343 [
11344 'webkitRequestFullscreen',
11345 'webkitExitFullscreen',
11346 'webkitFullscreenElement',
11347 'webkitFullscreenEnabled',
11348 'webkitfullscreenchange',
11349 'webkitfullscreenerror'
11350
11351 ],
11352 // Old WebKit (Safari 5.1)
11353 [
11354 'webkitRequestFullScreen',
11355 'webkitCancelFullScreen',
11356 'webkitCurrentFullScreenElement',
11357 'webkitCancelFullScreen',
11358 'webkitfullscreenchange',
11359 'webkitfullscreenerror'
11360
11361 ],
11362 [
11363 'mozRequestFullScreen',
11364 'mozCancelFullScreen',
11365 'mozFullScreenElement',
11366 'mozFullScreenEnabled',
11367 'mozfullscreenchange',
11368 'mozfullscreenerror'
11369 ],
11370 [
11371 'msRequestFullscreen',
11372 'msExitFullscreen',
11373 'msFullscreenElement',
11374 'msFullscreenEnabled',
11375 'MSFullscreenChange',
11376 'MSFullscreenError'
11377 ]
11378 ];
11379
11380 var i = 0;
11381 var l = fnMap.length;
11382 var ret = {};
11383
11384 for (; i < l; i++) {
11385 val = fnMap[i];
11386 if (val && val[1] in document) {
11387 for (i = 0; i < val.length; i++) {
11388 ret[fnMap[0][i]] = val[i];
11389 }
11390 return ret;
11391 }
11392 }
11393
11394 return false;
11395 })();
11396
11397 var eventNameMap = {
11398 change: fn.fullscreenchange,
11399 error: fn.fullscreenerror
11400 };
11401
11402 var ngscreenfull = {
11403 request: function (elem) {
11404 return new Promise(function (resolve) {
11405 var request = fn.requestFullscreen;
11406
11407 var onFullScreenEntered = function () {
11408 this.off('change', onFullScreenEntered);
11409 resolve();
11410 }.bind(this);
11411
11412 elem = elem || document.documentElement;
11413
11414 // Work around Safari 5.1 bug: reports support for
11415 // keyboard in fullscreen even though it doesn't.
11416 // Browser sniffing, since the alternative with
11417 // setTimeout is even worse.
11418 if (/ Version\/5\.1(?:\.\d+)? Safari\//.test(navigator.userAgent)) {
11419 elem[request]();
11420 } else {
11421 elem[request](keyboardAllowed ? Element.ALLOW_KEYBOARD_INPUT : {});
11422 }
11423
11424 this.on('change', onFullScreenEntered);
11425 }.bind(this));
11426 },
11427 exit: function () {
11428 return new Promise(function (resolve) {
11429 if (!this.isFullscreen) {
11430 resolve();
11431 return;
11432 }
11433
11434 var onFullScreenExit = function () {
11435 this.off('change', onFullScreenExit);
11436 resolve();
11437 }.bind(this);
11438
11439 document[fn.exitFullscreen]();
11440
11441 this.on('change', onFullScreenExit);
11442 }.bind(this));
11443 },
11444 toggle: function (elem) {
11445 return this.isFullscreen ? this.exit() : this.request(elem);
11446 },
11447 onchange: function (callback) {
11448 this.on('change', callback);
11449 },
11450 onerror: function (callback) {
11451 this.on('error', callback);
11452 },
11453 on: function (event, callback) {
11454 var eventName = eventNameMap[event];
11455 if (eventName) {
11456 document.addEventListener(eventName, callback, false);
11457 }
11458 },
11459 off: function (event, callback) {
11460 var eventName = eventNameMap[event];
11461 if (eventName) {
11462 document.removeEventListener(eventName, callback, false);
11463 }
11464 },
11465 raw: fn
11466 };
11467
11468 if (!fn) {
11469 if (isCommonjs) {
11470 module.exports = false;
11471 } else {
11472 window.ngscreenfull = false;
11473 }
11474
11475 return;
11476 }
11477
11478 Object.defineProperties(ngscreenfull, {
11479 isFullscreen: {
11480 get: function () {
11481 return Boolean(document[fn.fullscreenElement]);
11482 }
11483 },
11484 element: {
11485 enumerable: true,
11486 get: function () {
11487 return document[fn.fullscreenElement];
11488 }
11489 },
11490 enabled: {
11491 enumerable: true,
11492 get: function () {
11493 // Coerce to boolean in case of old WebKit
11494 return Boolean(document[fn.fullscreenEnabled]);
11495 }
11496 }
11497 });
11498
11499 if (isCommonjs) {
11500 module.exports = ngscreenfull;
11501 } else {
11502 window.ngscreenfull = ngscreenfull;
11503 }
11504})();
11505
11506
11507
11508//##########################################################################################################################
11509//## Shifty ################################################################################################################
11510//##########################################################################################################################
11511
11512 /*!
11513 * Shifty
11514 * By Jeremy Kahn - jeremyckahn@gmail.com
11515 */
11516
11517// external module EMBEDED in nanogallery
11518// NGY BUILD:
11519//
11520// replace "Tweenable" with "NGTweenable"
11521// replace "define.amd" with "define.amdDISABLED"
11522/* shifty - v1.5.3 - 2016-11-29 - http://jeremyckahn.github.io/shifty */
11523;(function () {
11524 var root = this || Function('return this')();
11525
11526/**
11527 * Shifty Core
11528 * By Jeremy Kahn - jeremyckahn@gmail.com
11529 */
11530
11531var NGTweenable = (function () {
11532
11533 'use strict';
11534
11535 // Aliases that get defined later in this function
11536 var formula;
11537
11538 // CONSTANTS
11539 var DEFAULT_SCHEDULE_FUNCTION;
11540 var DEFAULT_EASING = 'linear';
11541 var DEFAULT_DURATION = 500;
11542 var UPDATE_TIME = 1000 / 60;
11543
11544 var _now = Date.now
11545 ? Date.now
11546 : function () {return +new Date();};
11547
11548 var now = typeof SHIFTY_DEBUG_NOW !== 'undefined' ? SHIFTY_DEBUG_NOW : _now;
11549
11550 if (typeof window !== 'undefined') {
11551 // requestAnimationFrame() shim by Paul Irish (modified for Shifty)
11552 // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
11553 DEFAULT_SCHEDULE_FUNCTION = window.requestAnimationFrame
11554 || window.webkitRequestAnimationFrame
11555 || window.oRequestAnimationFrame
11556 || window.msRequestAnimationFrame
11557 || (window.mozCancelRequestAnimationFrame
11558 && window.mozRequestAnimationFrame)
11559 || setTimeout;
11560 } else {
11561 DEFAULT_SCHEDULE_FUNCTION = setTimeout;
11562 }
11563
11564 function noop () {
11565 // NOOP!
11566 }
11567
11568 /**
11569 * Handy shortcut for doing a for-in loop. This is not a "normal" each
11570 * function, it is optimized for Shifty. The iterator function only receives
11571 * the property name, not the value.
11572 * @param {Object} obj
11573 * @param {Function(string)} fn
11574 * @private
11575 */
11576 function each (obj, fn) {
11577 var key;
11578 for (key in obj) {
11579 if (Object.hasOwnProperty.call(obj, key)) {
11580 fn(key);
11581 }
11582 }
11583 }
11584
11585 /**
11586 * Perform a shallow copy of Object properties.
11587 * @param {Object} targetObject The object to copy into
11588 * @param {Object} srcObject The object to copy from
11589 * @return {Object} A reference to the augmented `targetObj` Object
11590 * @private
11591 */
11592 function shallowCopy (targetObj, srcObj) {
11593 each(srcObj, function (prop) {
11594 targetObj[prop] = srcObj[prop];
11595 });
11596
11597 return targetObj;
11598 }
11599
11600 /**
11601 * Copies each property from src onto target, but only if the property to
11602 * copy to target is undefined.
11603 * @param {Object} target Missing properties in this Object are filled in
11604 * @param {Object} src
11605 * @private
11606 */
11607 function defaults (target, src) {
11608 each(src, function (prop) {
11609 if (typeof target[prop] === 'undefined') {
11610 target[prop] = src[prop];
11611 }
11612 });
11613 }
11614
11615 /**
11616 * Calculates the interpolated tween values of an Object for a given
11617 * timestamp.
11618 * @param {Number} forPosition The position to compute the state for.
11619 * @param {Object} currentState Current state properties.
11620 * @param {Object} originalState: The original state properties the Object is
11621 * tweening from.
11622 * @param {Object} targetState: The destination state properties the Object
11623 * is tweening to.
11624 * @param {number} duration: The length of the tween in milliseconds.
11625 * @param {number} timestamp: The UNIX epoch time at which the tween began.
11626 * @param {Object} easing: This Object's keys must correspond to the keys in
11627 * targetState.
11628 * @private
11629 */
11630 function tweenProps (forPosition, currentState, originalState, targetState,
11631 duration, timestamp, easing) {
11632 var normalizedPosition =
11633 forPosition < timestamp ? 0 : (forPosition - timestamp) / duration;
11634
11635
11636 var prop;
11637 var easingObjectProp;
11638 var easingFn;
11639 for (prop in currentState) {
11640 if (currentState.hasOwnProperty(prop)) {
11641 easingObjectProp = easing[prop];
11642 easingFn = typeof easingObjectProp === 'function'
11643 ? easingObjectProp
11644 : formula[easingObjectProp];
11645
11646 currentState[prop] = tweenProp(
11647 originalState[prop],
11648 targetState[prop],
11649 easingFn,
11650 normalizedPosition
11651 );
11652 }
11653 }
11654
11655 return currentState;
11656 }
11657
11658 /**
11659 * Tweens a single property.
11660 * @param {number} start The value that the tween started from.
11661 * @param {number} end The value that the tween should end at.
11662 * @param {Function} easingFunc The easing curve to apply to the tween.
11663 * @param {number} position The normalized position (between 0.0 and 1.0) to
11664 * calculate the midpoint of 'start' and 'end' against.
11665 * @return {number} The tweened value.
11666 * @private
11667 */
11668 function tweenProp (start, end, easingFunc, position) {
11669 return start + (end - start) * easingFunc(position);
11670 }
11671
11672 /**
11673 * Applies a filter to NGTweenable instance.
11674 * @param {NGTweenable} tweenable The `NGTweenable` instance to call the filter
11675 * upon.
11676 * @param {String} filterName The name of the filter to apply.
11677 * @private
11678 */
11679 function applyFilter (tweenable, filterName) {
11680 var filters = NGTweenable.prototype.filter;
11681 var args = tweenable._filterArgs;
11682
11683 each(filters, function (name) {
11684 if (typeof filters[name][filterName] !== 'undefined') {
11685 filters[name][filterName].apply(tweenable, args);
11686 }
11687 });
11688 }
11689
11690 var timeoutHandler_endTime;
11691 var timeoutHandler_currentTime;
11692 var timeoutHandler_isEnded;
11693 var timeoutHandler_offset;
11694 /**
11695 * Handles the update logic for one step of a tween.
11696 * @param {NGTweenable} tweenable
11697 * @param {number} timestamp
11698 * @param {number} delay
11699 * @param {number} duration
11700 * @param {Object} currentState
11701 * @param {Object} originalState
11702 * @param {Object} targetState
11703 * @param {Object} easing
11704 * @param {Function(Object, *, number)} step
11705 * @param {Function(Function,number)}} schedule
11706 * @param {number=} opt_currentTimeOverride Needed for accurate timestamp in
11707 * NGTweenable#seek.
11708 * @private
11709 */
11710 function timeoutHandler (tweenable, timestamp, delay, duration, currentState,
11711 originalState, targetState, easing, step, schedule,
11712 opt_currentTimeOverride) {
11713
11714 timeoutHandler_endTime = timestamp + delay + duration;
11715
11716 timeoutHandler_currentTime =
11717 Math.min(opt_currentTimeOverride || now(), timeoutHandler_endTime);
11718
11719 timeoutHandler_isEnded =
11720 timeoutHandler_currentTime >= timeoutHandler_endTime;
11721
11722 timeoutHandler_offset = duration - (
11723 timeoutHandler_endTime - timeoutHandler_currentTime);
11724
11725 if (tweenable.isPlaying()) {
11726 if (timeoutHandler_isEnded) {
11727 step(targetState, tweenable._attachment, timeoutHandler_offset);
11728 tweenable.stop(true);
11729 } else {
11730 tweenable._scheduleId =
11731 schedule(tweenable._timeoutHandler, UPDATE_TIME);
11732
11733 applyFilter(tweenable, 'beforeTween');
11734
11735 // If the animation has not yet reached the start point (e.g., there was
11736 // delay that has not yet completed), just interpolate the starting
11737 // position of the tween.
11738 if (timeoutHandler_currentTime < (timestamp + delay)) {
11739 tweenProps(1, currentState, originalState, targetState, 1, 1, easing);
11740 } else {
11741 tweenProps(timeoutHandler_currentTime, currentState, originalState,
11742 targetState, duration, timestamp + delay, easing);
11743 }
11744
11745 applyFilter(tweenable, 'afterTween');
11746
11747 step(currentState, tweenable._attachment, timeoutHandler_offset);
11748 }
11749 }
11750 }
11751
11752
11753 /**
11754 * Creates a usable easing Object from a string, a function or another easing
11755 * Object. If `easing` is an Object, then this function clones it and fills
11756 * in the missing properties with `"linear"`.
11757 * @param {Object.<string|Function>} fromTweenParams
11758 * @param {Object|string|Function} easing
11759 * @return {Object.<string|Function>}
11760 * @private
11761 */
11762 function composeEasingObject (fromTweenParams, easing) {
11763 var composedEasing = {};
11764 var typeofEasing = typeof easing;
11765
11766 if (typeofEasing === 'string' || typeofEasing === 'function') {
11767 each(fromTweenParams, function (prop) {
11768 composedEasing[prop] = easing;
11769 });
11770 } else {
11771 each(fromTweenParams, function (prop) {
11772 if (!composedEasing[prop]) {
11773 composedEasing[prop] = easing[prop] || DEFAULT_EASING;
11774 }
11775 });
11776 }
11777
11778 return composedEasing;
11779 }
11780
11781 /**
11782 * NGTweenable constructor.
11783 * @class NGTweenable
11784 * @param {Object=} opt_initialState The values that the initial tween should
11785 * start at if a `from` object is not provided to `{{#crossLink
11786 * "NGTweenable/tween:method"}}{{/crossLink}}` or `{{#crossLink
11787 * "NGTweenable/setConfig:method"}}{{/crossLink}}`.
11788 * @param {Object=} opt_config Configuration object to be passed to
11789 * `{{#crossLink "NGTweenable/setConfig:method"}}{{/crossLink}}`.
11790 * @module NGTweenable
11791 * @constructor
11792 */
11793 function NGTweenable (opt_initialState, opt_config) {
11794 this._currentState = opt_initialState || {};
11795 this._configured = false;
11796 this._scheduleFunction = DEFAULT_SCHEDULE_FUNCTION;
11797
11798 // To prevent unnecessary calls to setConfig do not set default
11799 // configuration here. Only set default configuration immediately before
11800 // tweening if none has been set.
11801 if (typeof opt_config !== 'undefined') {
11802 this.setConfig(opt_config);
11803 }
11804 }
11805
11806 /**
11807 * Configure and start a tween.
11808 * @method tween
11809 * @param {Object=} opt_config Configuration object to be passed to
11810 * `{{#crossLink "NGTweenable/setConfig:method"}}{{/crossLink}}`.
11811 * @chainable
11812 */
11813 NGTweenable.prototype.tween = function (opt_config) {
11814 if (this._isTweening) {
11815 return this;
11816 }
11817
11818 // Only set default config if no configuration has been set previously and
11819 // none is provided now.
11820 if (opt_config !== undefined || !this._configured) {
11821 this.setConfig(opt_config);
11822 }
11823
11824 this._timestamp = now();
11825 this._start(this.get(), this._attachment);
11826 return this.resume();
11827 };
11828
11829 /**
11830 * Configure a tween that will start at some point in the future.
11831 *
11832 * @method setConfig
11833 * @param {Object} config The following values are valid:
11834 * - __from__ (_Object=_): Starting position. If omitted, `{{#crossLink
11835 * "NGTweenable/get:method"}}get(){{/crossLink}}` is used.
11836 * - __to__ (_Object=_): Ending position.
11837 * - __duration__ (_number=_): How many milliseconds to animate for.
11838 * - __delay__ (_delay=_): How many milliseconds to wait before starting the
11839 * tween.
11840 * - __start__ (_Function(Object, *)_): Function to execute when the tween
11841 * begins. Receives the state of the tween as the first parameter and
11842 * `attachment` as the second parameter.
11843 * - __step__ (_Function(Object, *, number)_): Function to execute on every
11844 * tick. Receives `{{#crossLink
11845 * "NGTweenable/get:method"}}get(){{/crossLink}}` as the first parameter,
11846 * `attachment` as the second parameter, and the time elapsed since the
11847 * start of the tween as the third. This function is not called on the
11848 * final step of the animation, but `finish` is.
11849 * - __finish__ (_Function(Object, *)_): Function to execute upon tween
11850 * completion. Receives the state of the tween as the first parameter and
11851 * `attachment` as the second parameter.
11852 * - __easing__ (_Object.<string|Function>|string|Function=_): Easing curve
11853 * name(s) or function(s) to use for the tween.
11854 * - __attachment__ (_*_): Cached value that is passed to the
11855 * `step`/`start`/`finish` methods.
11856 * @chainable
11857 */
11858 NGTweenable.prototype.setConfig = function (config) {
11859 config = config || {};
11860 this._configured = true;
11861
11862 // Attach something to this NGTweenable instance (e.g.: a DOM element, an
11863 // object, a string, etc.);
11864 this._attachment = config.attachment;
11865
11866 // Init the internal state
11867 this._pausedAtTime = null;
11868 this._scheduleId = null;
11869 this._delay = config.delay || 0;
11870 this._start = config.start || noop;
11871 this._step = config.step || noop;
11872 this._finish = config.finish || noop;
11873 this._duration = config.duration || DEFAULT_DURATION;
11874 this._currentState = shallowCopy({}, config.from || this.get());
11875 this._originalState = this.get();
11876 this._targetState = shallowCopy({}, config.to || this.get());
11877
11878 var self = this;
11879 this._timeoutHandler = function () {
11880 timeoutHandler(self,
11881 self._timestamp,
11882 self._delay,
11883 self._duration,
11884 self._currentState,
11885 self._originalState,
11886 self._targetState,
11887 self._easing,
11888 self._step,
11889 self._scheduleFunction
11890 );
11891 };
11892
11893 // Aliases used below
11894 var currentState = this._currentState;
11895 var targetState = this._targetState;
11896
11897 // Ensure that there is always something to tween to.
11898 defaults(targetState, currentState);
11899
11900 this._easing = composeEasingObject(
11901 currentState, config.easing || DEFAULT_EASING);
11902
11903 this._filterArgs =
11904 [currentState, this._originalState, targetState, this._easing];
11905
11906 applyFilter(this, 'tweenCreated');
11907 return this;
11908 };
11909
11910 /**
11911 * @method get
11912 * @return {Object} The current state.
11913 */
11914 NGTweenable.prototype.get = function () {
11915 return shallowCopy({}, this._currentState);
11916 };
11917
11918 /**
11919 * @method set
11920 * @param {Object} state The current state.
11921 */
11922 NGTweenable.prototype.set = function (state) {
11923 this._currentState = state;
11924 };
11925
11926 /**
11927 * Pause a tween. Paused tweens can be resumed from the point at which they
11928 * were paused. This is different from `{{#crossLink
11929 * "NGTweenable/stop:method"}}{{/crossLink}}`, as that method
11930 * causes a tween to start over when it is resumed.
11931 * @method pause
11932 * @chainable
11933 */
11934 NGTweenable.prototype.pause = function () {
11935 this._pausedAtTime = now();
11936 this._isPaused = true;
11937 return this;
11938 };
11939
11940 /**
11941 * Resume a paused tween.
11942 * @method resume
11943 * @chainable
11944 */
11945 NGTweenable.prototype.resume = function () {
11946 if (this._isPaused) {
11947 this._timestamp += now() - this._pausedAtTime;
11948 }
11949
11950 this._isPaused = false;
11951 this._isTweening = true;
11952
11953 this._timeoutHandler();
11954
11955 return this;
11956 };
11957
11958 /**
11959 * Move the state of the animation to a specific point in the tween's
11960 * timeline. If the animation is not running, this will cause the `step`
11961 * handlers to be called.
11962 * @method seek
11963 * @param {millisecond} millisecond The millisecond of the animation to seek
11964 * to. This must not be less than `0`.
11965 * @chainable
11966 */
11967 NGTweenable.prototype.seek = function (millisecond) {
11968 millisecond = Math.max(millisecond, 0);
11969 var currentTime = now();
11970
11971 if ((this._timestamp + millisecond) === 0) {
11972 return this;
11973 }
11974
11975 this._timestamp = currentTime - millisecond;
11976
11977 if (!this.isPlaying()) {
11978 this._isTweening = true;
11979 this._isPaused = false;
11980
11981 // If the animation is not running, call timeoutHandler to make sure that
11982 // any step handlers are run.
11983 timeoutHandler(this,
11984 this._timestamp,
11985 this._delay,
11986 this._duration,
11987 this._currentState,
11988 this._originalState,
11989 this._targetState,
11990 this._easing,
11991 this._step,
11992 this._scheduleFunction,
11993 currentTime
11994 );
11995
11996 this.pause();
11997 }
11998
11999 return this;
12000 };
12001
12002 /**
12003 * Stops and cancels a tween.
12004 * @param {boolean=} gotoEnd If `false` or omitted, the tween just stops at
12005 * its current state, and the `finish` handler is not invoked. If `true`,
12006 * the tweened object's values are instantly set to the target values, and
12007 * `finish` is invoked.
12008 * @method stop
12009 * @chainable
12010 */
12011 NGTweenable.prototype.stop = function (gotoEnd) {
12012 this._isTweening = false;
12013 this._isPaused = false;
12014 this._timeoutHandler = noop;
12015
12016 (root.cancelAnimationFrame ||
12017 root.webkitCancelAnimationFrame ||
12018 root.oCancelAnimationFrame ||
12019 root.msCancelAnimationFrame ||
12020 root.mozCancelRequestAnimationFrame ||
12021 root.clearTimeout)(this._scheduleId);
12022
12023 if (gotoEnd) {
12024 applyFilter(this, 'beforeTween');
12025 tweenProps(
12026 1,
12027 this._currentState,
12028 this._originalState,
12029 this._targetState,
12030 1,
12031 0,
12032 this._easing
12033 );
12034 applyFilter(this, 'afterTween');
12035 applyFilter(this, 'afterTweenEnd');
12036 this._finish.call(this, this._currentState, this._attachment);
12037 }
12038
12039 return this;
12040 };
12041
12042 /**
12043 * @method isPlaying
12044 * @return {boolean} Whether or not a tween is running.
12045 */
12046 NGTweenable.prototype.isPlaying = function () {
12047 return this._isTweening && !this._isPaused;
12048 };
12049
12050 /**
12051 * Set a custom schedule function.
12052 *
12053 * If a custom function is not set,
12054 * [`requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/Web/API/window.requestAnimationFrame)
12055 * is used if available, otherwise
12056 * [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/Window.setTimeout)
12057 * is used.
12058 * @method setScheduleFunction
12059 * @param {Function(Function,number)} scheduleFunction The function to be
12060 * used to schedule the next frame to be rendered.
12061 */
12062 NGTweenable.prototype.setScheduleFunction = function (scheduleFunction) {
12063 this._scheduleFunction = scheduleFunction;
12064 };
12065
12066 /**
12067 * `delete` all "own" properties. Call this when the `NGTweenable` instance
12068 * is no longer needed to free memory.
12069 * @method dispose
12070 */
12071 NGTweenable.prototype.dispose = function () {
12072 var prop;
12073 for (prop in this) {
12074 if (this.hasOwnProperty(prop)) {
12075 delete this[prop];
12076 }
12077 }
12078 };
12079
12080 /**
12081 * Filters are used for transforming the properties of a tween at various
12082 * points in a NGTweenable's life cycle. See the README for more info on this.
12083 * @private
12084 */
12085 NGTweenable.prototype.filter = {};
12086
12087 /**
12088 * This object contains all of the tweens available to Shifty. It is
12089 * extensible - simply attach properties to the `NGTweenable.prototype.formula`
12090 * Object following the same format as `linear`.
12091 *
12092 * `pos` should be a normalized `number` (between 0 and 1).
12093 * @property formula
12094 * @type {Object(function)}
12095 */
12096 NGTweenable.prototype.formula = {
12097 linear: function (pos) {
12098 return pos;
12099 }
12100 };
12101
12102 formula = NGTweenable.prototype.formula;
12103
12104 shallowCopy(NGTweenable, {
12105 'now': now
12106 ,'each': each
12107 ,'tweenProps': tweenProps
12108 ,'tweenProp': tweenProp
12109 ,'applyFilter': applyFilter
12110 ,'shallowCopy': shallowCopy
12111 ,'defaults': defaults
12112 ,'composeEasingObject': composeEasingObject
12113 });
12114
12115 // `root` is provided in the intro/outro files.
12116
12117 // A hook used for unit testing.
12118 if (typeof SHIFTY_DEBUG_NOW === 'function') {
12119 root.timeoutHandler = timeoutHandler;
12120 }
12121
12122 // Bootstrap NGTweenable appropriately for the environment.
12123 if (typeof exports === 'object') {
12124 // CommonJS
12125 module.exports = NGTweenable;
12126 } else if (typeof define === 'function' && define.amdDISABLED) {
12127 // AMD
12128 define(function () {return NGTweenable;});
12129 } else if (typeof root.NGTweenable === 'undefined') {
12130 // Browser: Make `NGTweenable` globally accessible.
12131 root.NGTweenable = NGTweenable;
12132 }
12133
12134 return NGTweenable;
12135
12136} ());
12137
12138/*!
12139 * All equations are adapted from Thomas Fuchs'
12140 * [Scripty2](https://github.com/madrobby/scripty2/blob/master/src/effects/transitions/penner.js).
12141 *
12142 * Based on Easing Equations (c) 2003 [Robert
12143 * Penner](http://www.robertpenner.com/), all rights reserved. This work is
12144 * [subject to terms](http://www.robertpenner.com/easing_terms_of_use.html).
12145 */
12146
12147/*!
12148 * TERMS OF USE - EASING EQUATIONS
12149 * Open source under the BSD License.
12150 * Easing Equations (c) 2003 Robert Penner, all rights reserved.
12151 */
12152
12153;(function () {
12154
12155 NGTweenable.shallowCopy(NGTweenable.prototype.formula, {
12156 easeInQuad: function (pos) {
12157 return Math.pow(pos, 2);
12158 },
12159
12160 easeOutQuad: function (pos) {
12161 return -(Math.pow((pos - 1), 2) - 1);
12162 },
12163
12164 easeInOutQuad: function (pos) {
12165 if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,2);}
12166 return -0.5 * ((pos -= 2) * pos - 2);
12167 },
12168
12169 easeInCubic: function (pos) {
12170 return Math.pow(pos, 3);
12171 },
12172
12173 easeOutCubic: function (pos) {
12174 return (Math.pow((pos - 1), 3) + 1);
12175 },
12176
12177 easeInOutCubic: function (pos) {
12178 if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,3);}
12179 return 0.5 * (Math.pow((pos - 2),3) + 2);
12180 },
12181
12182 easeInQuart: function (pos) {
12183 return Math.pow(pos, 4);
12184 },
12185
12186 easeOutQuart: function (pos) {
12187 return -(Math.pow((pos - 1), 4) - 1);
12188 },
12189
12190 easeInOutQuart: function (pos) {
12191 if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,4);}
12192 return -0.5 * ((pos -= 2) * Math.pow(pos,3) - 2);
12193 },
12194
12195 easeInQuint: function (pos) {
12196 return Math.pow(pos, 5);
12197 },
12198
12199 easeOutQuint: function (pos) {
12200 return (Math.pow((pos - 1), 5) + 1);
12201 },
12202
12203 easeInOutQuint: function (pos) {
12204 if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,5);}
12205 return 0.5 * (Math.pow((pos - 2),5) + 2);
12206 },
12207
12208 easeInSine: function (pos) {
12209 return -Math.cos(pos * (Math.PI / 2)) + 1;
12210 },
12211
12212 easeOutSine: function (pos) {
12213 return Math.sin(pos * (Math.PI / 2));
12214 },
12215
12216 easeInOutSine: function (pos) {
12217 return (-0.5 * (Math.cos(Math.PI * pos) - 1));
12218 },
12219
12220 easeInExpo: function (pos) {
12221 return (pos === 0) ? 0 : Math.pow(2, 10 * (pos - 1));
12222 },
12223
12224 easeOutExpo: function (pos) {
12225 return (pos === 1) ? 1 : -Math.pow(2, -10 * pos) + 1;
12226 },
12227
12228 easeInOutExpo: function (pos) {
12229 if (pos === 0) {return 0;}
12230 if (pos === 1) {return 1;}
12231 if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(2,10 * (pos - 1));}
12232 return 0.5 * (-Math.pow(2, -10 * --pos) + 2);
12233 },
12234
12235 easeInCirc: function (pos) {
12236 return -(Math.sqrt(1 - (pos * pos)) - 1);
12237 },
12238
12239 easeOutCirc: function (pos) {
12240 return Math.sqrt(1 - Math.pow((pos - 1), 2));
12241 },
12242
12243 easeInOutCirc: function (pos) {
12244 if ((pos /= 0.5) < 1) {return -0.5 * (Math.sqrt(1 - pos * pos) - 1);}
12245 return 0.5 * (Math.sqrt(1 - (pos -= 2) * pos) + 1);
12246 },
12247
12248 easeOutBounce: function (pos) {
12249 if ((pos) < (1 / 2.75)) {
12250 return (7.5625 * pos * pos);
12251 } else if (pos < (2 / 2.75)) {
12252 return (7.5625 * (pos -= (1.5 / 2.75)) * pos + 0.75);
12253 } else if (pos < (2.5 / 2.75)) {
12254 return (7.5625 * (pos -= (2.25 / 2.75)) * pos + 0.9375);
12255 } else {
12256 return (7.5625 * (pos -= (2.625 / 2.75)) * pos + 0.984375);
12257 }
12258 },
12259
12260 easeInBack: function (pos) {
12261 var s = 1.70158;
12262 return (pos) * pos * ((s + 1) * pos - s);
12263 },
12264
12265 easeOutBack: function (pos) {
12266 var s = 1.70158;
12267 return (pos = pos - 1) * pos * ((s + 1) * pos + s) + 1;
12268 },
12269
12270 easeInOutBack: function (pos) {
12271 var s = 1.70158;
12272 if ((pos /= 0.5) < 1) {
12273 return 0.5 * (pos * pos * (((s *= (1.525)) + 1) * pos - s));
12274 }
12275 return 0.5 * ((pos -= 2) * pos * (((s *= (1.525)) + 1) * pos + s) + 2);
12276 },
12277
12278 elastic: function (pos) {
12279 // jshint maxlen:90
12280 return -1 * Math.pow(4,-8 * pos) * Math.sin((pos * 6 - 1) * (2 * Math.PI) / 2) + 1;
12281 },
12282
12283 swingFromTo: function (pos) {
12284 var s = 1.70158;
12285 return ((pos /= 0.5) < 1) ?
12286 0.5 * (pos * pos * (((s *= (1.525)) + 1) * pos - s)) :
12287 0.5 * ((pos -= 2) * pos * (((s *= (1.525)) + 1) * pos + s) + 2);
12288 },
12289
12290 swingFrom: function (pos) {
12291 var s = 1.70158;
12292 return pos * pos * ((s + 1) * pos - s);
12293 },
12294
12295 swingTo: function (pos) {
12296 var s = 1.70158;
12297 return (pos -= 1) * pos * ((s + 1) * pos + s) + 1;
12298 },
12299
12300 bounce: function (pos) {
12301 if (pos < (1 / 2.75)) {
12302 return (7.5625 * pos * pos);
12303 } else if (pos < (2 / 2.75)) {
12304 return (7.5625 * (pos -= (1.5 / 2.75)) * pos + 0.75);
12305 } else if (pos < (2.5 / 2.75)) {
12306 return (7.5625 * (pos -= (2.25 / 2.75)) * pos + 0.9375);
12307 } else {
12308 return (7.5625 * (pos -= (2.625 / 2.75)) * pos + 0.984375);
12309 }
12310 },
12311
12312 bouncePast: function (pos) {
12313 if (pos < (1 / 2.75)) {
12314 return (7.5625 * pos * pos);
12315 } else if (pos < (2 / 2.75)) {
12316 return 2 - (7.5625 * (pos -= (1.5 / 2.75)) * pos + 0.75);
12317 } else if (pos < (2.5 / 2.75)) {
12318 return 2 - (7.5625 * (pos -= (2.25 / 2.75)) * pos + 0.9375);
12319 } else {
12320 return 2 - (7.5625 * (pos -= (2.625 / 2.75)) * pos + 0.984375);
12321 }
12322 },
12323
12324 easeFromTo: function (pos) {
12325 if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,4);}
12326 return -0.5 * ((pos -= 2) * Math.pow(pos,3) - 2);
12327 },
12328
12329 easeFrom: function (pos) {
12330 return Math.pow(pos,4);
12331 },
12332
12333 easeTo: function (pos) {
12334 return Math.pow(pos,0.25);
12335 }
12336 });
12337
12338}());
12339
12340// jshint maxlen:100
12341/**
12342 * The Bezier magic in this file is adapted/copied almost wholesale from
12343 * [Scripty2](https://github.com/madrobby/scripty2/blob/master/src/effects/transitions/cubic-bezier.js),
12344 * which was adapted from Apple code (which probably came from
12345 * [here](http://opensource.apple.com/source/WebCore/WebCore-955.66/platform/graphics/UnitBezier.h)).
12346 * Special thanks to Apple and Thomas Fuchs for much of this code.
12347 */
12348
12349/**
12350 * Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
12351 *
12352 * Redistribution and use in source and binary forms, with or without
12353 * modification, are permitted provided that the following conditions are met:
12354 *
12355 * 1. Redistributions of source code must retain the above copyright notice,
12356 * this list of conditions and the following disclaimer.
12357 *
12358 * 2. Redistributions in binary form must reproduce the above copyright notice,
12359 * this list of conditions and the following disclaimer in the documentation
12360 * and/or other materials provided with the distribution.
12361 *
12362 * 3. Neither the name of the copyright holder(s) nor the names of any
12363 * contributors may be used to endorse or promote products derived from
12364 * this software without specific prior written permission.
12365 *
12366 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
12367 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
12368 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
12369 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
12370 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
12371 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
12372 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
12373 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
12374 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
12375 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
12376 * POSSIBILITY OF SUCH DAMAGE.
12377 */
12378;(function () {
12379 // port of webkit cubic bezier handling by http://www.netzgesta.de/dev/
12380 function cubicBezierAtTime(t,p1x,p1y,p2x,p2y,duration) {
12381 var ax = 0,bx = 0,cx = 0,ay = 0,by = 0,cy = 0;
12382 function sampleCurveX(t) {
12383 return ((ax * t + bx) * t + cx) * t;
12384 }
12385 function sampleCurveY(t) {
12386 return ((ay * t + by) * t + cy) * t;
12387 }
12388 function sampleCurveDerivativeX(t) {
12389 return (3.0 * ax * t + 2.0 * bx) * t + cx;
12390 }
12391 function solveEpsilon(duration) {
12392 return 1.0 / (200.0 * duration);
12393 }
12394 function solve(x,epsilon) {
12395 return sampleCurveY(solveCurveX(x, epsilon));
12396 }
12397 function fabs(n) {
12398 if (n >= 0) {
12399 return n;
12400 } else {
12401 return 0 - n;
12402 }
12403 }
12404 function solveCurveX(x, epsilon) {
12405 var t0,t1,t2,x2,d2,i;
12406 for (t2 = x, i = 0; i < 8; i++) {
12407 x2 = sampleCurveX(t2) - x;
12408 if (fabs(x2) < epsilon) {
12409 return t2;
12410 }
12411 d2 = sampleCurveDerivativeX(t2);
12412 if (fabs(d2) < 1e-6) {
12413 break;
12414 }
12415 t2 = t2 - x2 / d2;
12416 }
12417 t0 = 0.0;
12418 t1 = 1.0;
12419 t2 = x;
12420 if (t2 < t0) {
12421 return t0;
12422 }
12423 if (t2 > t1) {
12424 return t1;
12425 }
12426 while (t0 < t1) {
12427 x2 = sampleCurveX(t2);
12428 if (fabs(x2 - x) < epsilon) {
12429 return t2;
12430 }
12431 if (x > x2) {
12432 t0 = t2;
12433 }else {
12434 t1 = t2;
12435 }
12436 t2 = (t1 - t0) * 0.5 + t0;
12437 }
12438 return t2; // Failure.
12439 }
12440 cx = 3.0 * p1x;
12441 bx = 3.0 * (p2x - p1x) - cx;
12442 ax = 1.0 - cx - bx;
12443 cy = 3.0 * p1y;
12444 by = 3.0 * (p2y - p1y) - cy;
12445 ay = 1.0 - cy - by;
12446 return solve(t, solveEpsilon(duration));
12447 }
12448 /**
12449 * getCubicBezierTransition(x1, y1, x2, y2) -> Function
12450 *
12451 * Generates a transition easing function that is compatible
12452 * with WebKit's CSS transitions `-webkit-transition-timing-function`
12453 * CSS property.
12454 *
12455 * The W3C has more information about CSS3 transition timing functions:
12456 * http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag
12457 *
12458 * @param {number} x1
12459 * @param {number} y1
12460 * @param {number} x2
12461 * @param {number} y2
12462 * @return {function}
12463 * @private
12464 */
12465 function getCubicBezierTransition (x1, y1, x2, y2) {
12466 return function (pos) {
12467 return cubicBezierAtTime(pos,x1,y1,x2,y2,1);
12468 };
12469 }
12470 // End ported code
12471
12472 /**
12473 * Create a Bezier easing function and attach it to `{{#crossLink
12474 * "NGTweenable/formula:property"}}NGTweenable#formula{{/crossLink}}`. This
12475 * function gives you total control over the easing curve. Matthew Lein's
12476 * [Ceaser](http://matthewlein.com/ceaser/) is a useful tool for visualizing
12477 * the curves you can make with this function.
12478 * @method setBezierFunction
12479 * @param {string} name The name of the easing curve. Overwrites the old
12480 * easing function on `{{#crossLink
12481 * "NGTweenable/formula:property"}}NGTweenable#formula{{/crossLink}}` if it
12482 * exists.
12483 * @param {number} x1
12484 * @param {number} y1
12485 * @param {number} x2
12486 * @param {number} y2
12487 * @return {function} The easing function that was attached to
12488 * NGTweenable.prototype.formula.
12489 */
12490 NGTweenable.setBezierFunction = function (name, x1, y1, x2, y2) {
12491 var cubicBezierTransition = getCubicBezierTransition(x1, y1, x2, y2);
12492 cubicBezierTransition.displayName = name;
12493 cubicBezierTransition.x1 = x1;
12494 cubicBezierTransition.y1 = y1;
12495 cubicBezierTransition.x2 = x2;
12496 cubicBezierTransition.y2 = y2;
12497
12498 return NGTweenable.prototype.formula[name] = cubicBezierTransition;
12499 };
12500
12501
12502 /**
12503 * `delete` an easing function from `{{#crossLink
12504 * "NGTweenable/formula:property"}}NGTweenable#formula{{/crossLink}}`. Be
12505 * careful with this method, as it `delete`s whatever easing formula matches
12506 * `name` (which means you can delete standard Shifty easing functions).
12507 * @method unsetBezierFunction
12508 * @param {string} name The name of the easing function to delete.
12509 * @return {function}
12510 */
12511 NGTweenable.unsetBezierFunction = function (name) {
12512 delete NGTweenable.prototype.formula[name];
12513 };
12514
12515})();
12516
12517;(function () {
12518
12519 function getInterpolatedValues (
12520 from, current, targetState, position, easing, delay) {
12521 return NGTweenable.tweenProps(
12522 position, current, from, targetState, 1, delay, easing);
12523 }
12524
12525 // Fake a NGTweenable and patch some internals. This approach allows us to
12526 // skip uneccessary processing and object recreation, cutting down on garbage
12527 // collection pauses.
12528 var mockNGTweenable = new NGTweenable();
12529 mockNGTweenable._filterArgs = [];
12530
12531 /**
12532 * Compute the midpoint of two Objects. This method effectively calculates a
12533 * specific frame of animation that `{{#crossLink
12534 * "NGTweenable/tween:method"}}{{/crossLink}}` does many times over the course
12535 * of a full tween.
12536 *
12537 * var interpolatedValues = NGTweenable.interpolate({
12538 * width: '100px',
12539 * opacity: 0,
12540 * color: '#fff'
12541 * }, {
12542 * width: '200px',
12543 * opacity: 1,
12544 * color: '#000'
12545 * }, 0.5);
12546 *
12547 * console.log(interpolatedValues);
12548 * // {opacity: 0.5, width: "150px", color: "rgb(127,127,127)"}
12549 *
12550 * @static
12551 * @method interpolate
12552 * @param {Object} from The starting values to tween from.
12553 * @param {Object} targetState The ending values to tween to.
12554 * @param {number} position The normalized position value (between `0.0` and
12555 * `1.0`) to interpolate the values between `from` and `to` for. `from`
12556 * represents `0` and `to` represents `1`.
12557 * @param {Object.<string|Function>|string|Function} easing The easing
12558 * curve(s) to calculate the midpoint against. You can reference any easing
12559 * function attached to `NGTweenable.prototype.formula`, or provide the easing
12560 * function(s) directly. If omitted, this defaults to "linear".
12561 * @param {number=} opt_delay Optional delay to pad the beginning of the
12562 * interpolated tween with. This increases the range of `position` from (`0`
12563 * through `1`) to (`0` through `1 + opt_delay`). So, a delay of `0.5` would
12564 * increase all valid values of `position` to numbers between `0` and `1.5`.
12565 * @return {Object}
12566 */
12567 NGTweenable.interpolate = function (
12568 from, targetState, position, easing, opt_delay) {
12569
12570 var current = NGTweenable.shallowCopy({}, from);
12571 var delay = opt_delay || 0;
12572 var easingObject = NGTweenable.composeEasingObject(
12573 from, easing || 'linear');
12574
12575 mockNGTweenable.set({});
12576
12577 // Alias and reuse the _filterArgs array instead of recreating it.
12578 var filterArgs = mockNGTweenable._filterArgs;
12579 filterArgs.length = 0;
12580 filterArgs[0] = current;
12581 filterArgs[1] = from;
12582 filterArgs[2] = targetState;
12583 filterArgs[3] = easingObject;
12584
12585 // Any defined value transformation must be applied
12586 NGTweenable.applyFilter(mockNGTweenable, 'tweenCreated');
12587 NGTweenable.applyFilter(mockNGTweenable, 'beforeTween');
12588
12589 var interpolatedValues = getInterpolatedValues(
12590 from, current, targetState, position, easingObject, delay);
12591
12592 // Transform values back into their original format
12593 NGTweenable.applyFilter(mockNGTweenable, 'afterTween');
12594
12595 return interpolatedValues;
12596 };
12597
12598}());
12599
12600/**
12601 * This module adds string interpolation support to Shifty.
12602 *
12603 * The Token extension allows Shifty to tween numbers inside of strings. Among
12604 * other things, this allows you to animate CSS properties. For example, you
12605 * can do this:
12606 *
12607 * var tweenable = new NGTweenable();
12608 * tweenable.tween({
12609 * from: { transform: 'translateX(45px)' },
12610 * to: { transform: 'translateX(90xp)' }
12611 * });
12612 *
12613 * `translateX(45)` will be tweened to `translateX(90)`. To demonstrate:
12614 *
12615 * var tweenable = new NGTweenable();
12616 * tweenable.tween({
12617 * from: { transform: 'translateX(45px)' },
12618 * to: { transform: 'translateX(90px)' },
12619 * step: function (state) {
12620 * console.log(state.transform);
12621 * }
12622 * });
12623 *
12624 * The above snippet will log something like this in the console:
12625 *
12626 * translateX(60.3px)
12627 * ...
12628 * translateX(76.05px)
12629 * ...
12630 * translateX(90px)
12631 *
12632 * Another use for this is animating colors:
12633 *
12634 * var tweenable = new NGTweenable();
12635 * tweenable.tween({
12636 * from: { color: 'rgb(0,255,0)' },
12637 * to: { color: 'rgb(255,0,255)' },
12638 * step: function (state) {
12639 * console.log(state.color);
12640 * }
12641 * });
12642 *
12643 * The above snippet will log something like this:
12644 *
12645 * rgb(84,170,84)
12646 * ...
12647 * rgb(170,84,170)
12648 * ...
12649 * rgb(255,0,255)
12650 *
12651 * This extension also supports hexadecimal colors, in both long (`#ff00ff`)
12652 * and short (`#f0f`) forms. Be aware that hexadecimal input values will be
12653 * converted into the equivalent RGB output values. This is done to optimize
12654 * for performance.
12655 *
12656 * var tweenable = new NGTweenable();
12657 * tweenable.tween({
12658 * from: { color: '#0f0' },
12659 * to: { color: '#f0f' },
12660 * step: function (state) {
12661 * console.log(state.color);
12662 * }
12663 * });
12664 *
12665 * This snippet will generate the same output as the one before it because
12666 * equivalent values were supplied (just in hexadecimal form rather than RGB):
12667 *
12668 * rgb(84,170,84)
12669 * ...
12670 * rgb(170,84,170)
12671 * ...
12672 * rgb(255,0,255)
12673 *
12674 * ## Easing support
12675 *
12676 * Easing works somewhat differently in the Token extension. This is because
12677 * some CSS properties have multiple values in them, and you might need to
12678 * tween each value along its own easing curve. A basic example:
12679 *
12680 * var tweenable = new NGTweenable();
12681 * tweenable.tween({
12682 * from: { transform: 'translateX(0px) translateY(0px)' },
12683 * to: { transform: 'translateX(100px) translateY(100px)' },
12684 * easing: { transform: 'easeInQuad' },
12685 * step: function (state) {
12686 * console.log(state.transform);
12687 * }
12688 * });
12689 *
12690 * The above snippet will create values like this:
12691 *
12692 * translateX(11.56px) translateY(11.56px)
12693 * ...
12694 * translateX(46.24px) translateY(46.24px)
12695 * ...
12696 * translateX(100px) translateY(100px)
12697 *
12698 * In this case, the values for `translateX` and `translateY` are always the
12699 * same for each step of the tween, because they have the same start and end
12700 * points and both use the same easing curve. We can also tween `translateX`
12701 * and `translateY` along independent curves:
12702 *
12703 * var tweenable = new NGTweenable();
12704 * tweenable.tween({
12705 * from: { transform: 'translateX(0px) translateY(0px)' },
12706 * to: { transform: 'translateX(100px) translateY(100px)' },
12707 * easing: { transform: 'easeInQuad bounce' },
12708 * step: function (state) {
12709 * console.log(state.transform);
12710 * }
12711 * });
12712 *
12713 * The above snippet will create values like this:
12714 *
12715 * translateX(10.89px) translateY(82.35px)
12716 * ...
12717 * translateX(44.89px) translateY(86.73px)
12718 * ...
12719 * translateX(100px) translateY(100px)
12720 *
12721 * `translateX` and `translateY` are not in sync anymore, because `easeInQuad`
12722 * was specified for `translateX` and `bounce` for `translateY`. Mixing and
12723 * matching easing curves can make for some interesting motion in your
12724 * animations.
12725 *
12726 * The order of the space-separated easing curves correspond the token values
12727 * they apply to. If there are more token values than easing curves listed,
12728 * the last easing curve listed is used.
12729 * @submodule NGTweenable.token
12730 */
12731
12732// token function is defined above only so that dox-foundation sees it as
12733// documentation and renders it. It is never used, and is optimized away at
12734// build time.
12735
12736;(function (NGTweenable) {
12737
12738 /**
12739 * @typedef {{
12740 * formatString: string
12741 * chunkNames: Array.<string>
12742 * }}
12743 * @private
12744 */
12745 var formatManifest;
12746
12747 // CONSTANTS
12748
12749 var R_NUMBER_COMPONENT = /(\d|\-|\.)/;
12750 var R_FORMAT_CHUNKS = /([^\-0-9\.]+)/g;
12751 var R_UNFORMATTED_VALUES = /[0-9.\-]+/g;
12752 var R_RGB = new RegExp(
12753 'rgb\\(' + R_UNFORMATTED_VALUES.source +
12754 (/,\s*/.source) + R_UNFORMATTED_VALUES.source +
12755 (/,\s*/.source) + R_UNFORMATTED_VALUES.source + '\\)', 'g');
12756 var R_RGB_PREFIX = /^.*\(/;
12757 var R_HEX = /#([0-9]|[a-f]){3,6}/gi;
12758 var VALUE_PLACEHOLDER = 'VAL';
12759
12760 // HELPERS
12761
12762 /**
12763 * @param {Array.number} rawValues
12764 * @param {string} prefix
12765 *
12766 * @return {Array.<string>}
12767 * @private
12768 */
12769 function getFormatChunksFrom (rawValues, prefix) {
12770 var accumulator = [];
12771
12772 var rawValuesLength = rawValues.length;
12773 var i;
12774
12775 for (i = 0; i < rawValuesLength; i++) {
12776 accumulator.push('_' + prefix + '_' + i);
12777 }
12778
12779 return accumulator;
12780 }
12781
12782 /**
12783 * @param {string} formattedString
12784 *
12785 * @return {string}
12786 * @private
12787 */
12788 function getFormatStringFrom (formattedString) {
12789 var chunks = formattedString.match(R_FORMAT_CHUNKS);
12790
12791 if (!chunks) {
12792 // chunks will be null if there were no tokens to parse in
12793 // formattedString (for example, if formattedString is '2'). Coerce
12794 // chunks to be useful here.
12795 chunks = ['', ''];
12796
12797 // If there is only one chunk, assume that the string is a number
12798 // followed by a token...
12799 // NOTE: This may be an unwise assumption.
12800 } else if (chunks.length === 1 ||
12801 // ...or if the string starts with a number component (".", "-", or a
12802 // digit)...
12803 formattedString.charAt(0).match(R_NUMBER_COMPONENT)) {
12804 // ...prepend an empty string here to make sure that the formatted number
12805 // is properly replaced by VALUE_PLACEHOLDER
12806 chunks.unshift('');
12807 }
12808
12809 return chunks.join(VALUE_PLACEHOLDER);
12810 }
12811
12812 /**
12813 * Convert all hex color values within a string to an rgb string.
12814 *
12815 * @param {Object} stateObject
12816 *
12817 * @return {Object} The modified obj
12818 * @private
12819 */
12820 function sanitizeObjectForHexProps (stateObject) {
12821 NGTweenable.each(stateObject, function (prop) {
12822 var currentProp = stateObject[prop];
12823
12824 if (typeof currentProp === 'string' && currentProp.match(R_HEX)) {
12825 stateObject[prop] = sanitizeHexChunksToRGB(currentProp);
12826 }
12827 });
12828 }
12829
12830 /**
12831 * @param {string} str
12832 *
12833 * @return {string}
12834 * @private
12835 */
12836 function sanitizeHexChunksToRGB (str) {
12837 return filterStringChunks(R_HEX, str, convertHexToRGB);
12838 }
12839
12840 /**
12841 * @param {string} hexString
12842 *
12843 * @return {string}
12844 * @private
12845 */
12846 function convertHexToRGB (hexString) {
12847 var rgbArr = hexToRGBArray(hexString);
12848 return 'rgb(' + rgbArr[0] + ',' + rgbArr[1] + ',' + rgbArr[2] + ')';
12849 }
12850
12851 var hexToRGBArray_returnArray = [];
12852 /**
12853 * Convert a hexadecimal string to an array with three items, one each for
12854 * the red, blue, and green decimal values.
12855 *
12856 * @param {string} hex A hexadecimal string.
12857 *
12858 * @returns {Array.<number>} The converted Array of RGB values if `hex` is a
12859 * valid string, or an Array of three 0's.
12860 * @private
12861 */
12862 function hexToRGBArray (hex) {
12863
12864 hex = hex.replace(/#/, '');
12865
12866 // If the string is a shorthand three digit hex notation, normalize it to
12867 // the standard six digit notation
12868 if (hex.length === 3) {
12869 hex = hex.split('');
12870 hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
12871 }
12872
12873 hexToRGBArray_returnArray[0] = hexToDec(hex.substr(0, 2));
12874 hexToRGBArray_returnArray[1] = hexToDec(hex.substr(2, 2));
12875 hexToRGBArray_returnArray[2] = hexToDec(hex.substr(4, 2));
12876
12877 return hexToRGBArray_returnArray;
12878 }
12879
12880 /**
12881 * Convert a base-16 number to base-10.
12882 *
12883 * @param {Number|String} hex The value to convert
12884 *
12885 * @returns {Number} The base-10 equivalent of `hex`.
12886 * @private
12887 */
12888 function hexToDec (hex) {
12889 return parseInt(hex, 16);
12890 }
12891
12892 /**
12893 * Runs a filter operation on all chunks of a string that match a RegExp
12894 *
12895 * @param {RegExp} pattern
12896 * @param {string} unfilteredString
12897 * @param {function(string)} filter
12898 *
12899 * @return {string}
12900 * @private
12901 */
12902 function filterStringChunks (pattern, unfilteredString, filter) {
12903 var pattenMatches = unfilteredString.match(pattern);
12904 var filteredString = unfilteredString.replace(pattern, VALUE_PLACEHOLDER);
12905
12906 if (pattenMatches) {
12907 var pattenMatchesLength = pattenMatches.length;
12908 var currentChunk;
12909
12910 for (var i = 0; i < pattenMatchesLength; i++) {
12911 currentChunk = pattenMatches.shift();
12912 filteredString = filteredString.replace(
12913 VALUE_PLACEHOLDER, filter(currentChunk));
12914 }
12915 }
12916
12917 return filteredString;
12918 }
12919
12920 /**
12921 * Check for floating point values within rgb strings and rounds them.
12922 *
12923 * @param {string} formattedString
12924 *
12925 * @return {string}
12926 * @private
12927 */
12928 function sanitizeRGBChunks (formattedString) {
12929 return filterStringChunks(R_RGB, formattedString, sanitizeRGBChunk);
12930 }
12931
12932 /**
12933 * @param {string} rgbChunk
12934 *
12935 * @return {string}
12936 * @private
12937 */
12938 function sanitizeRGBChunk (rgbChunk) {
12939 var numbers = rgbChunk.match(R_UNFORMATTED_VALUES);
12940 var numbersLength = numbers.length;
12941 var sanitizedString = rgbChunk.match(R_RGB_PREFIX)[0];
12942
12943 for (var i = 0; i < numbersLength; i++) {
12944 sanitizedString += parseInt(numbers[i], 10) + ',';
12945 }
12946
12947 sanitizedString = sanitizedString.slice(0, -1) + ')';
12948
12949 return sanitizedString;
12950 }
12951
12952 /**
12953 * @param {Object} stateObject
12954 *
12955 * @return {Object} An Object of formatManifests that correspond to
12956 * the string properties of stateObject
12957 * @private
12958 */
12959 function getFormatManifests (stateObject) {
12960 var manifestAccumulator = {};
12961
12962 NGTweenable.each(stateObject, function (prop) {
12963 var currentProp = stateObject[prop];
12964
12965 if (typeof currentProp === 'string') {
12966 var rawValues = getValuesFrom(currentProp);
12967
12968 manifestAccumulator[prop] = {
12969 'formatString': getFormatStringFrom(currentProp)
12970 ,'chunkNames': getFormatChunksFrom(rawValues, prop)
12971 };
12972 }
12973 });
12974
12975 return manifestAccumulator;
12976 }
12977
12978 /**
12979 * @param {Object} stateObject
12980 * @param {Object} formatManifests
12981 * @private
12982 */
12983 function expandFormattedProperties (stateObject, formatManifests) {
12984 NGTweenable.each(formatManifests, function (prop) {
12985 var currentProp = stateObject[prop];
12986 var rawValues = getValuesFrom(currentProp);
12987 var rawValuesLength = rawValues.length;
12988
12989 for (var i = 0; i < rawValuesLength; i++) {
12990 stateObject[formatManifests[prop].chunkNames[i]] = +rawValues[i];
12991 }
12992
12993 delete stateObject[prop];
12994 });
12995 }
12996
12997 /**
12998 * @param {Object} stateObject
12999 * @param {Object} formatManifests
13000 * @private
13001 */
13002 function collapseFormattedProperties (stateObject, formatManifests) {
13003 NGTweenable.each(formatManifests, function (prop) {
13004 var currentProp = stateObject[prop];
13005 var formatChunks = extractPropertyChunks(
13006 stateObject, formatManifests[prop].chunkNames);
13007 var valuesList = getValuesList(
13008 formatChunks, formatManifests[prop].chunkNames);
13009 currentProp = getFormattedValues(
13010 formatManifests[prop].formatString, valuesList);
13011 stateObject[prop] = sanitizeRGBChunks(currentProp);
13012 });
13013 }
13014
13015 /**
13016 * @param {Object} stateObject
13017 * @param {Array.<string>} chunkNames
13018 *
13019 * @return {Object} The extracted value chunks.
13020 * @private
13021 */
13022 function extractPropertyChunks (stateObject, chunkNames) {
13023 var extractedValues = {};
13024 var currentChunkName, chunkNamesLength = chunkNames.length;
13025
13026 for (var i = 0; i < chunkNamesLength; i++) {
13027 currentChunkName = chunkNames[i];
13028 extractedValues[currentChunkName] = stateObject[currentChunkName];
13029 delete stateObject[currentChunkName];
13030 }
13031
13032 return extractedValues;
13033 }
13034
13035 var getValuesList_accumulator = [];
13036 /**
13037 * @param {Object} stateObject
13038 * @param {Array.<string>} chunkNames
13039 *
13040 * @return {Array.<number>}
13041 * @private
13042 */
13043 function getValuesList (stateObject, chunkNames) {
13044 getValuesList_accumulator.length = 0;
13045 var chunkNamesLength = chunkNames.length;
13046
13047 for (var i = 0; i < chunkNamesLength; i++) {
13048 getValuesList_accumulator.push(stateObject[chunkNames[i]]);
13049 }
13050
13051 return getValuesList_accumulator;
13052 }
13053
13054 /**
13055 * @param {string} formatString
13056 * @param {Array.<number>} rawValues
13057 *
13058 * @return {string}
13059 * @private
13060 */
13061 function getFormattedValues (formatString, rawValues) {
13062 var formattedValueString = formatString;
13063 var rawValuesLength = rawValues.length;
13064
13065 for (var i = 0; i < rawValuesLength; i++) {
13066 formattedValueString = formattedValueString.replace(
13067 VALUE_PLACEHOLDER, +rawValues[i].toFixed(4));
13068 }
13069
13070 return formattedValueString;
13071 }
13072
13073 /**
13074 * Note: It's the duty of the caller to convert the Array elements of the
13075 * return value into numbers. This is a performance optimization.
13076 *
13077 * @param {string} formattedString
13078 *
13079 * @return {Array.<string>|null}
13080 * @private
13081 */
13082 function getValuesFrom (formattedString) {
13083 return formattedString.match(R_UNFORMATTED_VALUES);
13084 }
13085
13086 /**
13087 * @param {Object} easingObject
13088 * @param {Object} tokenData
13089 * @private
13090 */
13091 function expandEasingObject (easingObject, tokenData) {
13092 NGTweenable.each(tokenData, function (prop) {
13093 var currentProp = tokenData[prop];
13094 var chunkNames = currentProp.chunkNames;
13095 var chunkLength = chunkNames.length;
13096
13097 var easing = easingObject[prop];
13098 var i;
13099
13100 if (typeof easing === 'string') {
13101 var easingChunks = easing.split(' ');
13102 var lastEasingChunk = easingChunks[easingChunks.length - 1];
13103
13104 for (i = 0; i < chunkLength; i++) {
13105 easingObject[chunkNames[i]] = easingChunks[i] || lastEasingChunk;
13106 }
13107
13108 } else {
13109 for (i = 0; i < chunkLength; i++) {
13110 easingObject[chunkNames[i]] = easing;
13111 }
13112 }
13113
13114 delete easingObject[prop];
13115 });
13116 }
13117
13118 /**
13119 * @param {Object} easingObject
13120 * @param {Object} tokenData
13121 * @private
13122 */
13123 function collapseEasingObject (easingObject, tokenData) {
13124 NGTweenable.each(tokenData, function (prop) {
13125 var currentProp = tokenData[prop];
13126 var chunkNames = currentProp.chunkNames;
13127 var chunkLength = chunkNames.length;
13128
13129 var firstEasing = easingObject[chunkNames[0]];
13130 var typeofEasings = typeof firstEasing;
13131
13132 if (typeofEasings === 'string') {
13133 var composedEasingString = '';
13134
13135 for (var i = 0; i < chunkLength; i++) {
13136 composedEasingString += ' ' + easingObject[chunkNames[i]];
13137 delete easingObject[chunkNames[i]];
13138 }
13139
13140 easingObject[prop] = composedEasingString.substr(1);
13141 } else {
13142 easingObject[prop] = firstEasing;
13143 }
13144 });
13145 }
13146
13147 NGTweenable.prototype.filter.token = {
13148 'tweenCreated': function (currentState, fromState, toState, easingObject) {
13149 sanitizeObjectForHexProps(currentState);
13150 sanitizeObjectForHexProps(fromState);
13151 sanitizeObjectForHexProps(toState);
13152 this._tokenData = getFormatManifests(currentState);
13153 },
13154
13155 'beforeTween': function (currentState, fromState, toState, easingObject) {
13156 expandEasingObject(easingObject, this._tokenData);
13157 expandFormattedProperties(currentState, this._tokenData);
13158 expandFormattedProperties(fromState, this._tokenData);
13159 expandFormattedProperties(toState, this._tokenData);
13160 },
13161
13162 'afterTween': function (currentState, fromState, toState, easingObject) {
13163 collapseFormattedProperties(currentState, this._tokenData);
13164 collapseFormattedProperties(fromState, this._tokenData);
13165 collapseFormattedProperties(toState, this._tokenData);
13166 collapseEasingObject(easingObject, this._tokenData);
13167 }
13168 };
13169
13170} (NGTweenable));
13171
13172}).call(null);
13173
13174
13175
13176
13177//##########################################################################################################################
13178//## HAMMER.JS #############################################################################################################
13179//##########################################################################################################################
13180
13181// HAMMER.JS
13182
13183// external module EMBEDED in nanogallery
13184// NGY BUILD:
13185// replace "Hammer" with "NGHammer" (case sensitive)
13186// replace "var SUPPORT_POINTER_EVENTS = prefixed(window, 'PointerEvent') !== undefined;" with "var SUPPORT_POINTER_EVENTS = false;"
13187// replace "define.amd" with "define.amdDISABLED"
13188
13189
13190
13191/*! NGHammer.JS - v2.0.7 - 2016-04-22
13192 * http://hammerjs.github.io/
13193 *
13194 * Copyright (c) 2016 Jorik Tangelder;
13195 * Licensed under the MIT license */
13196(function(window, document, exportName, undefined) {
13197 'use strict';
13198
13199var VENDOR_PREFIXES = ['', 'webkit', 'Moz', 'MS', 'ms', 'o'];
13200var TEST_ELEMENT = document.createElement('div');
13201
13202var TYPE_FUNCTION = 'function';
13203
13204var round = Math.round;
13205var abs = Math.abs;
13206var now = Date.now;
13207
13208/**
13209 * set a timeout with a given scope
13210 * @param {Function} fn
13211 * @param {Number} timeout
13212 * @param {Object} context
13213 * @returns {number}
13214 */
13215function setTimeoutContext(fn, timeout, context) {
13216 return setTimeout(bindFn(fn, context), timeout);
13217}
13218
13219/**
13220 * if the argument is an array, we want to execute the fn on each entry
13221 * if it aint an array we don't want to do a thing.
13222 * this is used by all the methods that accept a single and array argument.
13223 * @param {*|Array} arg
13224 * @param {String} fn
13225 * @param {Object} [context]
13226 * @returns {Boolean}
13227 */
13228function invokeArrayArg(arg, fn, context) {
13229 if (Array.isArray(arg)) {
13230 each(arg, context[fn], context);
13231 return true;
13232 }
13233 return false;
13234}
13235
13236/**
13237 * walk objects and arrays
13238 * @param {Object} obj
13239 * @param {Function} iterator
13240 * @param {Object} context
13241 */
13242function each(obj, iterator, context) {
13243 var i;
13244
13245 if (!obj) {
13246 return;
13247 }
13248
13249 if (obj.forEach) {
13250 obj.forEach(iterator, context);
13251 } else if (obj.length !== undefined) {
13252 i = 0;
13253 while (i < obj.length) {
13254 iterator.call(context, obj[i], i, obj);
13255 i++;
13256 }
13257 } else {
13258 for (i in obj) {
13259 obj.hasOwnProperty(i) && iterator.call(context, obj[i], i, obj);
13260 }
13261 }
13262}
13263
13264/**
13265 * wrap a method with a deprecation warning and stack trace
13266 * @param {Function} method
13267 * @param {String} name
13268 * @param {String} message
13269 * @returns {Function} A new function wrapping the supplied method.
13270 */
13271function deprecate(method, name, message) {
13272 var deprecationMessage = 'DEPRECATED METHOD: ' + name + '\n' + message + ' AT \n';
13273 return function() {
13274 var e = new Error('get-stack-trace');
13275 var stack = e && e.stack ? e.stack.replace(/^[^\(]+?[\n$]/gm, '')
13276 .replace(/^\s+at\s+/gm, '')
13277 .replace(/^Object.<anonymous>\s*\(/gm, '{anonymous}()@') : 'Unknown Stack Trace';
13278
13279 var log = window.console && (window.console.warn || window.console.log);
13280 if (log) {
13281 log.call(window.console, deprecationMessage, stack);
13282 }
13283 return method.apply(this, arguments);
13284 };
13285}
13286
13287/**
13288 * extend object.
13289 * means that properties in dest will be overwritten by the ones in src.
13290 * @param {Object} target
13291 * @param {...Object} objects_to_assign
13292 * @returns {Object} target
13293 */
13294var assign;
13295if (typeof Object.assign !== 'function') {
13296 assign = function assign(target) {
13297 if (target === undefined || target === null) {
13298 throw new TypeError('Cannot convert undefined or null to object');
13299 }
13300
13301 var output = Object(target);
13302 for (var index = 1; index < arguments.length; index++) {
13303 var source = arguments[index];
13304 if (source !== undefined && source !== null) {
13305 for (var nextKey in source) {
13306 if (source.hasOwnProperty(nextKey)) {
13307 output[nextKey] = source[nextKey];
13308 }
13309 }
13310 }
13311 }
13312 return output;
13313 };
13314} else {
13315 assign = Object.assign;
13316}
13317
13318/**
13319 * extend object.
13320 * means that properties in dest will be overwritten by the ones in src.
13321 * @param {Object} dest
13322 * @param {Object} src
13323 * @param {Boolean} [merge=false]
13324 * @returns {Object} dest
13325 */
13326var extend = deprecate(function extend(dest, src, merge) {
13327 var keys = Object.keys(src);
13328 var i = 0;
13329 while (i < keys.length) {
13330 if (!merge || (merge && dest[keys[i]] === undefined)) {
13331 dest[keys[i]] = src[keys[i]];
13332 }
13333 i++;
13334 }
13335 return dest;
13336}, 'extend', 'Use `assign`.');
13337
13338/**
13339 * merge the values from src in the dest.
13340 * means that properties that exist in dest will not be overwritten by src
13341 * @param {Object} dest
13342 * @param {Object} src
13343 * @returns {Object} dest
13344 */
13345var merge = deprecate(function merge(dest, src) {
13346 return extend(dest, src, true);
13347}, 'merge', 'Use `assign`.');
13348
13349/**
13350 * simple class inheritance
13351 * @param {Function} child
13352 * @param {Function} base
13353 * @param {Object} [properties]
13354 */
13355function inherit(child, base, properties) {
13356 var baseP = base.prototype,
13357 childP;
13358
13359 childP = child.prototype = Object.create(baseP);
13360 childP.constructor = child;
13361 childP._super = baseP;
13362
13363 if (properties) {
13364 assign(childP, properties);
13365 }
13366}
13367
13368/**
13369 * simple function bind
13370 * @param {Function} fn
13371 * @param {Object} context
13372 * @returns {Function}
13373 */
13374function bindFn(fn, context) {
13375 return function boundFn() {
13376 return fn.apply(context, arguments);
13377 };
13378}
13379
13380/**
13381 * let a boolean value also be a function that must return a boolean
13382 * this first item in args will be used as the context
13383 * @param {Boolean|Function} val
13384 * @param {Array} [args]
13385 * @returns {Boolean}
13386 */
13387function boolOrFn(val, args) {
13388 if (typeof val == TYPE_FUNCTION) {
13389 return val.apply(args ? args[0] || undefined : undefined, args);
13390 }
13391 return val;
13392}
13393
13394/**
13395 * use the val2 when val1 is undefined
13396 * @param {*} val1
13397 * @param {*} val2
13398 * @returns {*}
13399 */
13400function ifUndefined(val1, val2) {
13401 return (val1 === undefined) ? val2 : val1;
13402}
13403
13404/**
13405 * addEventListener with multiple events at once
13406 * @param {EventTarget} target
13407 * @param {String} types
13408 * @param {Function} handler
13409 */
13410function addEventListeners(target, types, handler) {
13411 each(splitStr(types), function(type) {
13412 target.addEventListener(type, handler, false);
13413 });
13414}
13415
13416/**
13417 * removeEventListener with multiple events at once
13418 * @param {EventTarget} target
13419 * @param {String} types
13420 * @param {Function} handler
13421 */
13422function removeEventListeners(target, types, handler) {
13423 each(splitStr(types), function(type) {
13424 target.removeEventListener(type, handler, false);
13425 });
13426}
13427
13428/**
13429 * find if a node is in the given parent
13430 * @method hasParent
13431 * @param {HTMLElement} node
13432 * @param {HTMLElement} parent
13433 * @return {Boolean} found
13434 */
13435function hasParent(node, parent) {
13436 while (node) {
13437 if (node == parent) {
13438 return true;
13439 }
13440 node = node.parentNode;
13441 }
13442 return false;
13443}
13444
13445/**
13446 * small indexOf wrapper
13447 * @param {String} str
13448 * @param {String} find
13449 * @returns {Boolean} found
13450 */
13451function inStr(str, find) {
13452 return str.indexOf(find) > -1;
13453}
13454
13455/**
13456 * split string on whitespace
13457 * @param {String} str
13458 * @returns {Array} words
13459 */
13460function splitStr(str) {
13461 return str.trim().split(/\s+/g);
13462}
13463
13464/**
13465 * find if a array contains the object using indexOf or a simple polyFill
13466 * @param {Array} src
13467 * @param {String} find
13468 * @param {String} [findByKey]
13469 * @return {Boolean|Number} false when not found, or the index
13470 */
13471function inArray(src, find, findByKey) {
13472 if (src.indexOf && !findByKey) {
13473 return src.indexOf(find);
13474 } else {
13475 var i = 0;
13476 while (i < src.length) {
13477 if ((findByKey && src[i][findByKey] == find) || (!findByKey && src[i] === find)) {
13478 return i;
13479 }
13480 i++;
13481 }
13482 return -1;
13483 }
13484}
13485
13486/**
13487 * convert array-like objects to real arrays
13488 * @param {Object} obj
13489 * @returns {Array}
13490 */
13491function toArray(obj) {
13492 return Array.prototype.slice.call(obj, 0);
13493}
13494
13495/**
13496 * unique array with objects based on a key (like 'id') or just by the array's value
13497 * @param {Array} src [{id:1},{id:2},{id:1}]
13498 * @param {String} [key]
13499 * @param {Boolean} [sort=False]
13500 * @returns {Array} [{id:1},{id:2}]
13501 */
13502function uniqueArray(src, key, sort) {
13503 var results = [];
13504 var values = [];
13505 var i = 0;
13506
13507 while (i < src.length) {
13508 var val = key ? src[i][key] : src[i];
13509 if (inArray(values, val) < 0) {
13510 results.push(src[i]);
13511 }
13512 values[i] = val;
13513 i++;
13514 }
13515
13516 if (sort) {
13517 if (!key) {
13518 results = results.sort();
13519 } else {
13520 results = results.sort(function sortUniqueArray(a, b) {
13521 return a[key] > b[key];
13522 });
13523 }
13524 }
13525
13526 return results;
13527}
13528
13529/**
13530 * get the prefixed property
13531 * @param {Object} obj
13532 * @param {String} property
13533 * @returns {String|Undefined} prefixed
13534 */
13535function prefixed(obj, property) {
13536 var prefix, prop;
13537 var camelProp = property[0].toUpperCase() + property.slice(1);
13538
13539 var i = 0;
13540 while (i < VENDOR_PREFIXES.length) {
13541 prefix = VENDOR_PREFIXES[i];
13542 prop = (prefix) ? prefix + camelProp : property;
13543
13544 if (prop in obj) {
13545 return prop;
13546 }
13547 i++;
13548 }
13549 return undefined;
13550}
13551
13552/**
13553 * get a unique id
13554 * @returns {number} uniqueId
13555 */
13556var _uniqueId = 1;
13557function uniqueId() {
13558 return _uniqueId++;
13559}
13560
13561/**
13562 * get the window object of an element
13563 * @param {HTMLElement} element
13564 * @returns {DocumentView|Window}
13565 */
13566function getWindowForElement(element) {
13567 var doc = element.ownerDocument || element;
13568 return (doc.defaultView || doc.parentWindow || window);
13569}
13570
13571var MOBILE_REGEX = /mobile|tablet|ip(ad|hone|od)|android/i;
13572
13573var SUPPORT_TOUCH = ('ontouchstart' in window);
13574// var SUPPORT_POINTER_EVENTS = prefixed(window, 'PointerEvent') !== undefined;
13575var SUPPORT_POINTER_EVENTS = false;
13576var SUPPORT_ONLY_TOUCH = SUPPORT_TOUCH && MOBILE_REGEX.test(navigator.userAgent);
13577
13578var INPUT_TYPE_TOUCH = 'touch';
13579var INPUT_TYPE_PEN = 'pen';
13580var INPUT_TYPE_MOUSE = 'mouse';
13581var INPUT_TYPE_KINECT = 'kinect';
13582
13583var COMPUTE_INTERVAL = 25;
13584
13585var INPUT_START = 1;
13586var INPUT_MOVE = 2;
13587var INPUT_END = 4;
13588var INPUT_CANCEL = 8;
13589
13590var DIRECTION_NONE = 1;
13591var DIRECTION_LEFT = 2;
13592var DIRECTION_RIGHT = 4;
13593var DIRECTION_UP = 8;
13594var DIRECTION_DOWN = 16;
13595
13596var DIRECTION_HORIZONTAL = DIRECTION_LEFT | DIRECTION_RIGHT;
13597var DIRECTION_VERTICAL = DIRECTION_UP | DIRECTION_DOWN;
13598var DIRECTION_ALL = DIRECTION_HORIZONTAL | DIRECTION_VERTICAL;
13599
13600var PROPS_XY = ['x', 'y'];
13601var PROPS_CLIENT_XY = ['clientX', 'clientY'];
13602
13603/**
13604 * create new input type manager
13605 * @param {Manager} manager
13606 * @param {Function} callback
13607 * @returns {Input}
13608 * @constructor
13609 */
13610function Input(manager, callback) {
13611 var self = this;
13612 this.manager = manager;
13613 this.callback = callback;
13614 this.element = manager.element;
13615 this.target = manager.options.inputTarget;
13616
13617 // smaller wrapper around the handler, for the scope and the enabled state of the manager,
13618 // so when disabled the input events are completely bypassed.
13619 this.domHandler = function(ev) {
13620 if (boolOrFn(manager.options.enable, [manager])) {
13621 self.handler(ev);
13622 }
13623 };
13624
13625 this.init();
13626
13627}
13628
13629Input.prototype = {
13630 /**
13631 * should handle the inputEvent data and trigger the callback
13632 * @virtual
13633 */
13634 handler: function() { },
13635
13636 /**
13637 * bind the events
13638 */
13639 init: function() {
13640 this.evEl && addEventListeners(this.element, this.evEl, this.domHandler);
13641 this.evTarget && addEventListeners(this.target, this.evTarget, this.domHandler);
13642 this.evWin && addEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler);
13643 },
13644
13645 /**
13646 * unbind the events
13647 */
13648 destroy: function() {
13649 this.evEl && removeEventListeners(this.element, this.evEl, this.domHandler);
13650 this.evTarget && removeEventListeners(this.target, this.evTarget, this.domHandler);
13651 this.evWin && removeEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler);
13652 }
13653};
13654
13655/**
13656 * create new input type manager
13657 * called by the Manager constructor
13658 * @param {NGHammer} manager
13659 * @returns {Input}
13660 */
13661function createInputInstance(manager) {
13662 var Type;
13663 var inputClass = manager.options.inputClass;
13664
13665 if (inputClass) {
13666 Type = inputClass;
13667 } else if (SUPPORT_POINTER_EVENTS) {
13668 Type = PointerEventInput;
13669 } else if (SUPPORT_ONLY_TOUCH) {
13670 Type = TouchInput;
13671 } else if (!SUPPORT_TOUCH) {
13672 Type = MouseInput;
13673 } else {
13674 Type = TouchMouseInput;
13675 }
13676 return new (Type)(manager, inputHandler);
13677}
13678
13679/**
13680 * handle input events
13681 * @param {Manager} manager
13682 * @param {String} eventType
13683 * @param {Object} input
13684 */
13685function inputHandler(manager, eventType, input) {
13686 var pointersLen = input.pointers.length;
13687 var changedPointersLen = input.changedPointers.length;
13688 var isFirst = (eventType & INPUT_START && (pointersLen - changedPointersLen === 0));
13689 var isFinal = (eventType & (INPUT_END | INPUT_CANCEL) && (pointersLen - changedPointersLen === 0));
13690
13691 input.isFirst = !!isFirst;
13692 input.isFinal = !!isFinal;
13693
13694 if (isFirst) {
13695 manager.session = {};
13696 }
13697
13698 // source event is the normalized value of the domEvents
13699 // like 'touchstart, mouseup, pointerdown'
13700 input.eventType = eventType;
13701
13702 // compute scale, rotation etc
13703 computeInputData(manager, input);
13704
13705 // emit secret event
13706 manager.emit('hammer.input', input);
13707
13708 manager.recognize(input);
13709 manager.session.prevInput = input;
13710}
13711
13712/**
13713 * extend the data with some usable properties like scale, rotate, velocity etc
13714 * @param {Object} manager
13715 * @param {Object} input
13716 */
13717function computeInputData(manager, input) {
13718 var session = manager.session;
13719 var pointers = input.pointers;
13720 var pointersLength = pointers.length;
13721
13722 // store the first input to calculate the distance and direction
13723 if (!session.firstInput) {
13724 session.firstInput = simpleCloneInputData(input);
13725 }
13726
13727 // to compute scale and rotation we need to store the multiple touches
13728 if (pointersLength > 1 && !session.firstMultiple) {
13729 session.firstMultiple = simpleCloneInputData(input);
13730 } else if (pointersLength === 1) {
13731 session.firstMultiple = false;
13732 }
13733
13734 var firstInput = session.firstInput;
13735 var firstMultiple = session.firstMultiple;
13736 var offsetCenter = firstMultiple ? firstMultiple.center : firstInput.center;
13737
13738 var center = input.center = getCenter(pointers);
13739 input.timeStamp = now();
13740 input.deltaTime = input.timeStamp - firstInput.timeStamp;
13741
13742 input.angle = getAngle(offsetCenter, center);
13743 input.distance = getDistance(offsetCenter, center);
13744
13745 computeDeltaXY(session, input);
13746 input.offsetDirection = getDirection(input.deltaX, input.deltaY);
13747
13748 var overallVelocity = getVelocity(input.deltaTime, input.deltaX, input.deltaY);
13749 input.overallVelocityX = overallVelocity.x;
13750 input.overallVelocityY = overallVelocity.y;
13751 input.overallVelocity = (abs(overallVelocity.x) > abs(overallVelocity.y)) ? overallVelocity.x : overallVelocity.y;
13752
13753 input.scale = firstMultiple ? getScale(firstMultiple.pointers, pointers) : 1;
13754 input.rotation = firstMultiple ? getRotation(firstMultiple.pointers, pointers) : 0;
13755
13756 input.maxPointers = !session.prevInput ? input.pointers.length : ((input.pointers.length >
13757 session.prevInput.maxPointers) ? input.pointers.length : session.prevInput.maxPointers);
13758
13759 computeIntervalInputData(session, input);
13760
13761 // find the correct target
13762 var target = manager.element;
13763 if (hasParent(input.srcEvent.target, target)) {
13764 target = input.srcEvent.target;
13765 }
13766 input.target = target;
13767}
13768
13769function computeDeltaXY(session, input) {
13770 var center = input.center;
13771 var offset = session.offsetDelta || {};
13772 var prevDelta = session.prevDelta || {};
13773 var prevInput = session.prevInput || {};
13774
13775 if (input.eventType === INPUT_START || prevInput.eventType === INPUT_END) {
13776 prevDelta = session.prevDelta = {
13777 x: prevInput.deltaX || 0,
13778 y: prevInput.deltaY || 0
13779 };
13780
13781 offset = session.offsetDelta = {
13782 x: center.x,
13783 y: center.y
13784 };
13785 }
13786
13787 input.deltaX = prevDelta.x + (center.x - offset.x);
13788 input.deltaY = prevDelta.y + (center.y - offset.y);
13789}
13790
13791/**
13792 * velocity is calculated every x ms
13793 * @param {Object} session
13794 * @param {Object} input
13795 */
13796function computeIntervalInputData(session, input) {
13797 var last = session.lastInterval || input,
13798 deltaTime = input.timeStamp - last.timeStamp,
13799 velocity, velocityX, velocityY, direction;
13800
13801 if (input.eventType != INPUT_CANCEL && (deltaTime > COMPUTE_INTERVAL || last.velocity === undefined)) {
13802 var deltaX = input.deltaX - last.deltaX;
13803 var deltaY = input.deltaY - last.deltaY;
13804
13805 var v = getVelocity(deltaTime, deltaX, deltaY);
13806 velocityX = v.x;
13807 velocityY = v.y;
13808 velocity = (abs(v.x) > abs(v.y)) ? v.x : v.y;
13809 direction = getDirection(deltaX, deltaY);
13810
13811 session.lastInterval = input;
13812 } else {
13813 // use latest velocity info if it doesn't overtake a minimum period
13814 velocity = last.velocity;
13815 velocityX = last.velocityX;
13816 velocityY = last.velocityY;
13817 direction = last.direction;
13818 }
13819
13820 input.velocity = velocity;
13821 input.velocityX = velocityX;
13822 input.velocityY = velocityY;
13823 input.direction = direction;
13824}
13825
13826/**
13827 * create a simple clone from the input used for storage of firstInput and firstMultiple
13828 * @param {Object} input
13829 * @returns {Object} clonedInputData
13830 */
13831function simpleCloneInputData(input) {
13832 // make a simple copy of the pointers because we will get a reference if we don't
13833 // we only need clientXY for the calculations
13834 var pointers = [];
13835 var i = 0;
13836 while (i < input.pointers.length) {
13837 pointers[i] = {
13838 clientX: round(input.pointers[i].clientX),
13839 clientY: round(input.pointers[i].clientY)
13840 };
13841 i++;
13842 }
13843
13844 return {
13845 timeStamp: now(),
13846 pointers: pointers,
13847 center: getCenter(pointers),
13848 deltaX: input.deltaX,
13849 deltaY: input.deltaY
13850 };
13851}
13852
13853/**
13854 * get the center of all the pointers
13855 * @param {Array} pointers
13856 * @return {Object} center contains `x` and `y` properties
13857 */
13858function getCenter(pointers) {
13859 var pointersLength = pointers.length;
13860
13861 // no need to loop when only one touch
13862 if (pointersLength === 1) {
13863 return {
13864 x: round(pointers[0].clientX),
13865 y: round(pointers[0].clientY)
13866 };
13867 }
13868
13869 var x = 0, y = 0, i = 0;
13870 while (i < pointersLength) {
13871 x += pointers[i].clientX;
13872 y += pointers[i].clientY;
13873 i++;
13874 }
13875
13876 return {
13877 x: round(x / pointersLength),
13878 y: round(y / pointersLength)
13879 };
13880}
13881
13882/**
13883 * calculate the velocity between two points. unit is in px per ms.
13884 * @param {Number} deltaTime
13885 * @param {Number} x
13886 * @param {Number} y
13887 * @return {Object} velocity `x` and `y`
13888 */
13889function getVelocity(deltaTime, x, y) {
13890 return {
13891 x: x / deltaTime || 0,
13892 y: y / deltaTime || 0
13893 };
13894}
13895
13896/**
13897 * get the direction between two points
13898 * @param {Number} x
13899 * @param {Number} y
13900 * @return {Number} direction
13901 */
13902function getDirection(x, y) {
13903 if (x === y) {
13904 return DIRECTION_NONE;
13905 }
13906
13907 if (abs(x) >= abs(y)) {
13908 return x < 0 ? DIRECTION_LEFT : DIRECTION_RIGHT;
13909 }
13910 return y < 0 ? DIRECTION_UP : DIRECTION_DOWN;
13911}
13912
13913/**
13914 * calculate the absolute distance between two points
13915 * @param {Object} p1 {x, y}
13916 * @param {Object} p2 {x, y}
13917 * @param {Array} [props] containing x and y keys
13918 * @return {Number} distance
13919 */
13920function getDistance(p1, p2, props) {
13921 if (!props) {
13922 props = PROPS_XY;
13923 }
13924 var x = p2[props[0]] - p1[props[0]],
13925 y = p2[props[1]] - p1[props[1]];
13926
13927 return Math.sqrt((x * x) + (y * y));
13928}
13929
13930/**
13931 * calculate the angle between two coordinates
13932 * @param {Object} p1
13933 * @param {Object} p2
13934 * @param {Array} [props] containing x and y keys
13935 * @return {Number} angle
13936 */
13937function getAngle(p1, p2, props) {
13938 if (!props) {
13939 props = PROPS_XY;
13940 }
13941 var x = p2[props[0]] - p1[props[0]],
13942 y = p2[props[1]] - p1[props[1]];
13943 return Math.atan2(y, x) * 180 / Math.PI;
13944}
13945
13946/**
13947 * calculate the rotation degrees between two pointersets
13948 * @param {Array} start array of pointers
13949 * @param {Array} end array of pointers
13950 * @return {Number} rotation
13951 */
13952function getRotation(start, end) {
13953 return getAngle(end[1], end[0], PROPS_CLIENT_XY) + getAngle(start[1], start[0], PROPS_CLIENT_XY);
13954}
13955
13956/**
13957 * calculate the scale factor between two pointersets
13958 * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out
13959 * @param {Array} start array of pointers
13960 * @param {Array} end array of pointers
13961 * @return {Number} scale
13962 */
13963function getScale(start, end) {
13964 return getDistance(end[0], end[1], PROPS_CLIENT_XY) / getDistance(start[0], start[1], PROPS_CLIENT_XY);
13965}
13966
13967var MOUSE_INPUT_MAP = {
13968 mousedown: INPUT_START,
13969 mousemove: INPUT_MOVE,
13970 mouseup: INPUT_END
13971};
13972
13973var MOUSE_ELEMENT_EVENTS = 'mousedown';
13974var MOUSE_WINDOW_EVENTS = 'mousemove mouseup';
13975
13976/**
13977 * Mouse events input
13978 * @constructor
13979 * @extends Input
13980 */
13981function MouseInput() {
13982 this.evEl = MOUSE_ELEMENT_EVENTS;
13983 this.evWin = MOUSE_WINDOW_EVENTS;
13984
13985 this.pressed = false; // mousedown state
13986
13987 Input.apply(this, arguments);
13988}
13989
13990inherit(MouseInput, Input, {
13991 /**
13992 * handle mouse events
13993 * @param {Object} ev
13994 */
13995 handler: function MEhandler(ev) {
13996 var eventType = MOUSE_INPUT_MAP[ev.type];
13997
13998 // on start we want to have the left mouse button down
13999 if (eventType & INPUT_START && ev.button === 0) {
14000 this.pressed = true;
14001 }
14002
14003 if (eventType & INPUT_MOVE && ev.which !== 1) {
14004 eventType = INPUT_END;
14005 }
14006
14007 // mouse must be down
14008 if (!this.pressed) {
14009 return;
14010 }
14011
14012 if (eventType & INPUT_END) {
14013 this.pressed = false;
14014 }
14015
14016 this.callback(this.manager, eventType, {
14017 pointers: [ev],
14018 changedPointers: [ev],
14019 pointerType: INPUT_TYPE_MOUSE,
14020 srcEvent: ev
14021 });
14022 }
14023});
14024
14025var POINTER_INPUT_MAP = {
14026 pointerdown: INPUT_START,
14027 pointermove: INPUT_MOVE,
14028 pointerup: INPUT_END,
14029 pointercancel: INPUT_CANCEL,
14030 pointerout: INPUT_CANCEL
14031};
14032
14033// in IE10 the pointer types is defined as an enum
14034var IE10_POINTER_TYPE_ENUM = {
14035 2: INPUT_TYPE_TOUCH,
14036 3: INPUT_TYPE_PEN,
14037 4: INPUT_TYPE_MOUSE,
14038 5: INPUT_TYPE_KINECT // see https://twitter.com/jacobrossi/status/480596438489890816
14039};
14040
14041var POINTER_ELEMENT_EVENTS = 'pointerdown';
14042var POINTER_WINDOW_EVENTS = 'pointermove pointerup pointercancel';
14043
14044// IE10 has prefixed support, and case-sensitive
14045if (window.MSPointerEvent && !window.PointerEvent) {
14046 POINTER_ELEMENT_EVENTS = 'MSPointerDown';
14047 POINTER_WINDOW_EVENTS = 'MSPointerMove MSPointerUp MSPointerCancel';
14048}
14049
14050/**
14051 * Pointer events input
14052 * @constructor
14053 * @extends Input
14054 */
14055function PointerEventInput() {
14056 this.evEl = POINTER_ELEMENT_EVENTS;
14057 this.evWin = POINTER_WINDOW_EVENTS;
14058
14059 Input.apply(this, arguments);
14060
14061 this.store = (this.manager.session.pointerEvents = []);
14062}
14063
14064inherit(PointerEventInput, Input, {
14065 /**
14066 * handle mouse events
14067 * @param {Object} ev
14068 */
14069 handler: function PEhandler(ev) {
14070 var store = this.store;
14071 var removePointer = false;
14072
14073 var eventTypeNormalized = ev.type.toLowerCase().replace('ms', '');
14074 var eventType = POINTER_INPUT_MAP[eventTypeNormalized];
14075 var pointerType = IE10_POINTER_TYPE_ENUM[ev.pointerType] || ev.pointerType;
14076
14077 var isTouch = (pointerType == INPUT_TYPE_TOUCH);
14078
14079 // get index of the event in the store
14080 var storeIndex = inArray(store, ev.pointerId, 'pointerId');
14081
14082 // start and mouse must be down
14083 if (eventType & INPUT_START && (ev.button === 0 || isTouch)) {
14084 if (storeIndex < 0) {
14085 store.push(ev);
14086 storeIndex = store.length - 1;
14087 }
14088 } else if (eventType & (INPUT_END | INPUT_CANCEL)) {
14089 removePointer = true;
14090 }
14091
14092 // it not found, so the pointer hasn't been down (so it's probably a hover)
14093 if (storeIndex < 0) {
14094 return;
14095 }
14096
14097 // update the event in the store
14098 store[storeIndex] = ev;
14099
14100 this.callback(this.manager, eventType, {
14101 pointers: store,
14102 changedPointers: [ev],
14103 pointerType: pointerType,
14104 srcEvent: ev
14105 });
14106
14107 if (removePointer) {
14108 // remove from the store
14109 store.splice(storeIndex, 1);
14110 }
14111 }
14112});
14113
14114var SINGLE_TOUCH_INPUT_MAP = {
14115 touchstart: INPUT_START,
14116 touchmove: INPUT_MOVE,
14117 touchend: INPUT_END,
14118 touchcancel: INPUT_CANCEL
14119};
14120
14121var SINGLE_TOUCH_TARGET_EVENTS = 'touchstart';
14122var SINGLE_TOUCH_WINDOW_EVENTS = 'touchstart touchmove touchend touchcancel';
14123
14124/**
14125 * Touch events input
14126 * @constructor
14127 * @extends Input
14128 */
14129function SingleTouchInput() {
14130 this.evTarget = SINGLE_TOUCH_TARGET_EVENTS;
14131 this.evWin = SINGLE_TOUCH_WINDOW_EVENTS;
14132 this.started = false;
14133
14134 Input.apply(this, arguments);
14135}
14136
14137inherit(SingleTouchInput, Input, {
14138 handler: function TEhandler(ev) {
14139 var type = SINGLE_TOUCH_INPUT_MAP[ev.type];
14140
14141 // should we handle the touch events?
14142 if (type === INPUT_START) {
14143 this.started = true;
14144 }
14145
14146 if (!this.started) {
14147 return;
14148 }
14149
14150 var touches = normalizeSingleTouches.call(this, ev, type);
14151
14152 // when done, reset the started state
14153 if (type & (INPUT_END | INPUT_CANCEL) && touches[0].length - touches[1].length === 0) {
14154 this.started = false;
14155 }
14156
14157 this.callback(this.manager, type, {
14158 pointers: touches[0],
14159 changedPointers: touches[1],
14160 pointerType: INPUT_TYPE_TOUCH,
14161 srcEvent: ev
14162 });
14163 }
14164});
14165
14166/**
14167 * @this {TouchInput}
14168 * @param {Object} ev
14169 * @param {Number} type flag
14170 * @returns {undefined|Array} [all, changed]
14171 */
14172function normalizeSingleTouches(ev, type) {
14173 var all = toArray(ev.touches);
14174 var changed = toArray(ev.changedTouches);
14175
14176 if (type & (INPUT_END | INPUT_CANCEL)) {
14177 all = uniqueArray(all.concat(changed), 'identifier', true);
14178 }
14179
14180 return [all, changed];
14181}
14182
14183var TOUCH_INPUT_MAP = {
14184 touchstart: INPUT_START,
14185 touchmove: INPUT_MOVE,
14186 touchend: INPUT_END,
14187 touchcancel: INPUT_CANCEL
14188};
14189
14190var TOUCH_TARGET_EVENTS = 'touchstart touchmove touchend touchcancel';
14191
14192/**
14193 * Multi-user touch events input
14194 * @constructor
14195 * @extends Input
14196 */
14197function TouchInput() {
14198 this.evTarget = TOUCH_TARGET_EVENTS;
14199 this.targetIds = {};
14200
14201 Input.apply(this, arguments);
14202}
14203
14204inherit(TouchInput, Input, {
14205 handler: function MTEhandler(ev) {
14206 var type = TOUCH_INPUT_MAP[ev.type];
14207 var touches = getTouches.call(this, ev, type);
14208 if (!touches) {
14209 return;
14210 }
14211
14212 this.callback(this.manager, type, {
14213 pointers: touches[0],
14214 changedPointers: touches[1],
14215 pointerType: INPUT_TYPE_TOUCH,
14216 srcEvent: ev
14217 });
14218 }
14219});
14220
14221/**
14222 * @this {TouchInput}
14223 * @param {Object} ev
14224 * @param {Number} type flag
14225 * @returns {undefined|Array} [all, changed]
14226 */
14227function getTouches(ev, type) {
14228 var allTouches = toArray(ev.touches);
14229 var targetIds = this.targetIds;
14230
14231 // when there is only one touch, the process can be simplified
14232 if (type & (INPUT_START | INPUT_MOVE) && allTouches.length === 1) {
14233 targetIds[allTouches[0].identifier] = true;
14234 return [allTouches, allTouches];
14235 }
14236
14237 var i,
14238 targetTouches,
14239 changedTouches = toArray(ev.changedTouches),
14240 changedTargetTouches = [],
14241 target = this.target;
14242
14243 // get target touches from touches
14244 targetTouches = allTouches.filter(function(touch) {
14245 return hasParent(touch.target, target);
14246 });
14247
14248 // collect touches
14249 if (type === INPUT_START) {
14250 i = 0;
14251 while (i < targetTouches.length) {
14252 targetIds[targetTouches[i].identifier] = true;
14253 i++;
14254 }
14255 }
14256
14257 // filter changed touches to only contain touches that exist in the collected target ids
14258 i = 0;
14259 while (i < changedTouches.length) {
14260 if (targetIds[changedTouches[i].identifier]) {
14261 changedTargetTouches.push(changedTouches[i]);
14262 }
14263
14264 // cleanup removed touches
14265 if (type & (INPUT_END | INPUT_CANCEL)) {
14266 delete targetIds[changedTouches[i].identifier];
14267 }
14268 i++;
14269 }
14270
14271 if (!changedTargetTouches.length) {
14272 return;
14273 }
14274
14275 return [
14276 // merge targetTouches with changedTargetTouches so it contains ALL touches, including 'end' and 'cancel'
14277 uniqueArray(targetTouches.concat(changedTargetTouches), 'identifier', true),
14278 changedTargetTouches
14279 ];
14280}
14281
14282/**
14283 * Combined touch and mouse input
14284 *
14285 * Touch has a higher priority then mouse, and while touching no mouse events are allowed.
14286 * This because touch devices also emit mouse events while doing a touch.
14287 *
14288 * @constructor
14289 * @extends Input
14290 */
14291
14292var DEDUP_TIMEOUT = 2500;
14293var DEDUP_DISTANCE = 25;
14294
14295function TouchMouseInput() {
14296 Input.apply(this, arguments);
14297
14298 var handler = bindFn(this.handler, this);
14299 this.touch = new TouchInput(this.manager, handler);
14300 this.mouse = new MouseInput(this.manager, handler);
14301
14302 this.primaryTouch = null;
14303 this.lastTouches = [];
14304}
14305
14306inherit(TouchMouseInput, Input, {
14307 /**
14308 * handle mouse and touch events
14309 * @param {NGHammer} manager
14310 * @param {String} inputEvent
14311 * @param {Object} inputData
14312 */
14313 handler: function TMEhandler(manager, inputEvent, inputData) {
14314 var isTouch = (inputData.pointerType == INPUT_TYPE_TOUCH),
14315 isMouse = (inputData.pointerType == INPUT_TYPE_MOUSE);
14316
14317 if (isMouse && inputData.sourceCapabilities && inputData.sourceCapabilities.firesTouchEvents) {
14318 return;
14319 }
14320
14321 // when we're in a touch event, record touches to de-dupe synthetic mouse event
14322 if (isTouch) {
14323 recordTouches.call(this, inputEvent, inputData);
14324 } else if (isMouse && isSyntheticEvent.call(this, inputData)) {
14325 return;
14326 }
14327
14328 this.callback(manager, inputEvent, inputData);
14329 },
14330
14331 /**
14332 * remove the event listeners
14333 */
14334 destroy: function destroy() {
14335 this.touch.destroy();
14336 this.mouse.destroy();
14337 }
14338});
14339
14340function recordTouches(eventType, eventData) {
14341 if (eventType & INPUT_START) {
14342 this.primaryTouch = eventData.changedPointers[0].identifier;
14343 setLastTouch.call(this, eventData);
14344 } else if (eventType & (INPUT_END | INPUT_CANCEL)) {
14345 setLastTouch.call(this, eventData);
14346 }
14347}
14348
14349function setLastTouch(eventData) {
14350 var touch = eventData.changedPointers[0];
14351
14352 if (touch.identifier === this.primaryTouch) {
14353 var lastTouch = {x: touch.clientX, y: touch.clientY};
14354 this.lastTouches.push(lastTouch);
14355 var lts = this.lastTouches;
14356 var removeLastTouch = function() {
14357 var i = lts.indexOf(lastTouch);
14358 if (i > -1) {
14359 lts.splice(i, 1);
14360 }
14361 };
14362 setTimeout(removeLastTouch, DEDUP_TIMEOUT);
14363 }
14364}
14365
14366function isSyntheticEvent(eventData) {
14367 var x = eventData.srcEvent.clientX, y = eventData.srcEvent.clientY;
14368 for (var i = 0; i < this.lastTouches.length; i++) {
14369 var t = this.lastTouches[i];
14370 var dx = Math.abs(x - t.x), dy = Math.abs(y - t.y);
14371 if (dx <= DEDUP_DISTANCE && dy <= DEDUP_DISTANCE) {
14372 return true;
14373 }
14374 }
14375 return false;
14376}
14377
14378var PREFIXED_TOUCH_ACTION = prefixed(TEST_ELEMENT.style, 'touchAction');
14379var NATIVE_TOUCH_ACTION = PREFIXED_TOUCH_ACTION !== undefined;
14380
14381// magical touchAction value
14382var TOUCH_ACTION_COMPUTE = 'compute';
14383var TOUCH_ACTION_AUTO = 'auto';
14384var TOUCH_ACTION_MANIPULATION = 'manipulation'; // not implemented
14385var TOUCH_ACTION_NONE = 'none';
14386var TOUCH_ACTION_PAN_X = 'pan-x';
14387var TOUCH_ACTION_PAN_Y = 'pan-y';
14388var TOUCH_ACTION_MAP = getTouchActionProps();
14389
14390/**
14391 * Touch Action
14392 * sets the touchAction property or uses the js alternative
14393 * @param {Manager} manager
14394 * @param {String} value
14395 * @constructor
14396 */
14397function TouchAction(manager, value) {
14398 this.manager = manager;
14399 this.set(value);
14400}
14401
14402TouchAction.prototype = {
14403 /**
14404 * set the touchAction value on the element or enable the polyfill
14405 * @param {String} value
14406 */
14407 set: function(value) {
14408 // find out the touch-action by the event handlers
14409 if (value == TOUCH_ACTION_COMPUTE) {
14410 value = this.compute();
14411 }
14412
14413 if (NATIVE_TOUCH_ACTION && this.manager.element.style && TOUCH_ACTION_MAP[value]) {
14414 this.manager.element.style[PREFIXED_TOUCH_ACTION] = value;
14415 }
14416 this.actions = value.toLowerCase().trim();
14417 },
14418
14419 /**
14420 * just re-set the touchAction value
14421 */
14422 update: function() {
14423 this.set(this.manager.options.touchAction);
14424 },
14425
14426 /**
14427 * compute the value for the touchAction property based on the recognizer's settings
14428 * @returns {String} value
14429 */
14430 compute: function() {
14431 var actions = [];
14432 each(this.manager.recognizers, function(recognizer) {
14433 if (boolOrFn(recognizer.options.enable, [recognizer])) {
14434 actions = actions.concat(recognizer.getTouchAction());
14435 }
14436 });
14437 return cleanTouchActions(actions.join(' '));
14438 },
14439
14440 /**
14441 * this method is called on each input cycle and provides the preventing of the browser behavior
14442 * @param {Object} input
14443 */
14444 preventDefaults: function(input) {
14445 var srcEvent = input.srcEvent;
14446 var direction = input.offsetDirection;
14447
14448 // if the touch action did prevented once this session
14449 if (this.manager.session.prevented) {
14450 srcEvent.preventDefault();
14451 return;
14452 }
14453
14454 var actions = this.actions;
14455 var hasNone = inStr(actions, TOUCH_ACTION_NONE) && !TOUCH_ACTION_MAP[TOUCH_ACTION_NONE];
14456 var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_Y];
14457 var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_X];
14458
14459 if (hasNone) {
14460 //do not prevent defaults if this is a tap gesture
14461
14462 var isTapPointer = input.pointers.length === 1;
14463 var isTapMovement = input.distance < 2;
14464 var isTapTouchTime = input.deltaTime < 250;
14465
14466 if (isTapPointer && isTapMovement && isTapTouchTime) {
14467 return;
14468 }
14469 }
14470
14471 if (hasPanX && hasPanY) {
14472 // `pan-x pan-y` means browser handles all scrolling/panning, do not prevent
14473 return;
14474 }
14475
14476 if (hasNone ||
14477 (hasPanY && direction & DIRECTION_HORIZONTAL) ||
14478 (hasPanX && direction & DIRECTION_VERTICAL)) {
14479 return this.preventSrc(srcEvent);
14480 }
14481 },
14482
14483 /**
14484 * call preventDefault to prevent the browser's default behavior (scrolling in most cases)
14485 * @param {Object} srcEvent
14486 */
14487 preventSrc: function(srcEvent) {
14488 this.manager.session.prevented = true;
14489 srcEvent.preventDefault();
14490 }
14491};
14492
14493/**
14494 * when the touchActions are collected they are not a valid value, so we need to clean things up. *
14495 * @param {String} actions
14496 * @returns {*}
14497 */
14498function cleanTouchActions(actions) {
14499 // none
14500 if (inStr(actions, TOUCH_ACTION_NONE)) {
14501 return TOUCH_ACTION_NONE;
14502 }
14503
14504 var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X);
14505 var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y);
14506
14507 // if both pan-x and pan-y are set (different recognizers
14508 // for different directions, e.g. horizontal pan but vertical swipe?)
14509 // we need none (as otherwise with pan-x pan-y combined none of these
14510 // recognizers will work, since the browser would handle all panning
14511 if (hasPanX && hasPanY) {
14512 return TOUCH_ACTION_NONE;
14513 }
14514
14515 // pan-x OR pan-y
14516 if (hasPanX || hasPanY) {
14517 return hasPanX ? TOUCH_ACTION_PAN_X : TOUCH_ACTION_PAN_Y;
14518 }
14519
14520 // manipulation
14521 if (inStr(actions, TOUCH_ACTION_MANIPULATION)) {
14522 return TOUCH_ACTION_MANIPULATION;
14523 }
14524
14525 return TOUCH_ACTION_AUTO;
14526}
14527
14528function getTouchActionProps() {
14529 if (!NATIVE_TOUCH_ACTION) {
14530 return false;
14531 }
14532 var touchMap = {};
14533 var cssSupports = window.CSS && window.CSS.supports;
14534 ['auto', 'manipulation', 'pan-y', 'pan-x', 'pan-x pan-y', 'none'].forEach(function(val) {
14535
14536 // If css.supports is not supported but there is native touch-action assume it supports
14537 // all values. This is the case for IE 10 and 11.
14538 touchMap[val] = cssSupports ? window.CSS.supports('touch-action', val) : true;
14539 });
14540 return touchMap;
14541}
14542
14543/**
14544 * Recognizer flow explained; *
14545 * All recognizers have the initial state of POSSIBLE when a input session starts.
14546 * The definition of a input session is from the first input until the last input, with all it's movement in it. *
14547 * Example session for mouse-input: mousedown -> mousemove -> mouseup
14548 *
14549 * On each recognizing cycle (see Manager.recognize) the .recognize() method is executed
14550 * which determines with state it should be.
14551 *
14552 * If the recognizer has the state FAILED, CANCELLED or RECOGNIZED (equals ENDED), it is reset to
14553 * POSSIBLE to give it another change on the next cycle.
14554 *
14555 * Possible
14556 * |
14557 * +-----+---------------+
14558 * | |
14559 * +-----+-----+ |
14560 * | | |
14561 * Failed Cancelled |
14562 * +-------+------+
14563 * | |
14564 * Recognized Began
14565 * |
14566 * Changed
14567 * |
14568 * Ended/Recognized
14569 */
14570var STATE_POSSIBLE = 1;
14571var STATE_BEGAN = 2;
14572var STATE_CHANGED = 4;
14573var STATE_ENDED = 8;
14574var STATE_RECOGNIZED = STATE_ENDED;
14575var STATE_CANCELLED = 16;
14576var STATE_FAILED = 32;
14577
14578/**
14579 * Recognizer
14580 * Every recognizer needs to extend from this class.
14581 * @constructor
14582 * @param {Object} options
14583 */
14584function Recognizer(options) {
14585 this.options = assign({}, this.defaults, options || {});
14586
14587 this.id = uniqueId();
14588
14589 this.manager = null;
14590
14591 // default is enable true
14592 this.options.enable = ifUndefined(this.options.enable, true);
14593
14594 this.state = STATE_POSSIBLE;
14595
14596 this.simultaneous = {};
14597 this.requireFail = [];
14598}
14599
14600Recognizer.prototype = {
14601 /**
14602 * @virtual
14603 * @type {Object}
14604 */
14605 defaults: {},
14606
14607 /**
14608 * set options
14609 * @param {Object} options
14610 * @return {Recognizer}
14611 */
14612 set: function(options) {
14613 assign(this.options, options);
14614
14615 // also update the touchAction, in case something changed about the directions/enabled state
14616 this.manager && this.manager.touchAction.update();
14617 return this;
14618 },
14619
14620 /**
14621 * recognize simultaneous with an other recognizer.
14622 * @param {Recognizer} otherRecognizer
14623 * @returns {Recognizer} this
14624 */
14625 recognizeWith: function(otherRecognizer) {
14626 if (invokeArrayArg(otherRecognizer, 'recognizeWith', this)) {
14627 return this;
14628 }
14629
14630 var simultaneous = this.simultaneous;
14631 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
14632 if (!simultaneous[otherRecognizer.id]) {
14633 simultaneous[otherRecognizer.id] = otherRecognizer;
14634 otherRecognizer.recognizeWith(this);
14635 }
14636 return this;
14637 },
14638
14639 /**
14640 * drop the simultaneous link. it doesnt remove the link on the other recognizer.
14641 * @param {Recognizer} otherRecognizer
14642 * @returns {Recognizer} this
14643 */
14644 dropRecognizeWith: function(otherRecognizer) {
14645 if (invokeArrayArg(otherRecognizer, 'dropRecognizeWith', this)) {
14646 return this;
14647 }
14648
14649 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
14650 delete this.simultaneous[otherRecognizer.id];
14651 return this;
14652 },
14653
14654 /**
14655 * recognizer can only run when an other is failing
14656 * @param {Recognizer} otherRecognizer
14657 * @returns {Recognizer} this
14658 */
14659 requireFailure: function(otherRecognizer) {
14660 if (invokeArrayArg(otherRecognizer, 'requireFailure', this)) {
14661 return this;
14662 }
14663
14664 var requireFail = this.requireFail;
14665 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
14666 if (inArray(requireFail, otherRecognizer) === -1) {
14667 requireFail.push(otherRecognizer);
14668 otherRecognizer.requireFailure(this);
14669 }
14670 return this;
14671 },
14672
14673 /**
14674 * drop the requireFailure link. it does not remove the link on the other recognizer.
14675 * @param {Recognizer} otherRecognizer
14676 * @returns {Recognizer} this
14677 */
14678 dropRequireFailure: function(otherRecognizer) {
14679 if (invokeArrayArg(otherRecognizer, 'dropRequireFailure', this)) {
14680 return this;
14681 }
14682
14683 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
14684 var index = inArray(this.requireFail, otherRecognizer);
14685 if (index > -1) {
14686 this.requireFail.splice(index, 1);
14687 }
14688 return this;
14689 },
14690
14691 /**
14692 * has require failures boolean
14693 * @returns {boolean}
14694 */
14695 hasRequireFailures: function() {
14696 return this.requireFail.length > 0;
14697 },
14698
14699 /**
14700 * if the recognizer can recognize simultaneous with an other recognizer
14701 * @param {Recognizer} otherRecognizer
14702 * @returns {Boolean}
14703 */
14704 canRecognizeWith: function(otherRecognizer) {
14705 return !!this.simultaneous[otherRecognizer.id];
14706 },
14707
14708 /**
14709 * You should use `tryEmit` instead of `emit` directly to check
14710 * that all the needed recognizers has failed before emitting.
14711 * @param {Object} input
14712 */
14713 emit: function(input) {
14714 var self = this;
14715 var state = this.state;
14716
14717 function emit(event) {
14718 self.manager.emit(event, input);
14719 }
14720
14721 // 'panstart' and 'panmove'
14722 if (state < STATE_ENDED) {
14723 emit(self.options.event + stateStr(state));
14724 }
14725
14726 emit(self.options.event); // simple 'eventName' events
14727
14728 if (input.additionalEvent) { // additional event(panleft, panright, pinchin, pinchout...)
14729 emit(input.additionalEvent);
14730 }
14731
14732 // panend and pancancel
14733 if (state >= STATE_ENDED) {
14734 emit(self.options.event + stateStr(state));
14735 }
14736 },
14737
14738 /**
14739 * Check that all the require failure recognizers has failed,
14740 * if true, it emits a gesture event,
14741 * otherwise, setup the state to FAILED.
14742 * @param {Object} input
14743 */
14744 tryEmit: function(input) {
14745 if (this.canEmit()) {
14746 return this.emit(input);
14747 }
14748 // it's failing anyway
14749 this.state = STATE_FAILED;
14750 },
14751
14752 /**
14753 * can we emit?
14754 * @returns {boolean}
14755 */
14756 canEmit: function() {
14757 var i = 0;
14758 while (i < this.requireFail.length) {
14759 if (!(this.requireFail[i].state & (STATE_FAILED | STATE_POSSIBLE))) {
14760 return false;
14761 }
14762 i++;
14763 }
14764 return true;
14765 },
14766
14767 /**
14768 * update the recognizer
14769 * @param {Object} inputData
14770 */
14771 recognize: function(inputData) {
14772 // make a new copy of the inputData
14773 // so we can change the inputData without messing up the other recognizers
14774 var inputDataClone = assign({}, inputData);
14775
14776 // is is enabled and allow recognizing?
14777 if (!boolOrFn(this.options.enable, [this, inputDataClone])) {
14778 this.reset();
14779 this.state = STATE_FAILED;
14780 return;
14781 }
14782
14783 // reset when we've reached the end
14784 if (this.state & (STATE_RECOGNIZED | STATE_CANCELLED | STATE_FAILED)) {
14785 this.state = STATE_POSSIBLE;
14786 }
14787
14788 this.state = this.process(inputDataClone);
14789
14790 // the recognizer has recognized a gesture
14791 // so trigger an event
14792 if (this.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED | STATE_CANCELLED)) {
14793 this.tryEmit(inputDataClone);
14794 }
14795 },
14796
14797 /**
14798 * return the state of the recognizer
14799 * the actual recognizing happens in this method
14800 * @virtual
14801 * @param {Object} inputData
14802 * @returns {Const} STATE
14803 */
14804 process: function(inputData) { }, // jshint ignore:line
14805
14806 /**
14807 * return the preferred touch-action
14808 * @virtual
14809 * @returns {Array}
14810 */
14811 getTouchAction: function() { },
14812
14813 /**
14814 * called when the gesture isn't allowed to recognize
14815 * like when another is being recognized or it is disabled
14816 * @virtual
14817 */
14818 reset: function() { }
14819};
14820
14821/**
14822 * get a usable string, used as event postfix
14823 * @param {Const} state
14824 * @returns {String} state
14825 */
14826function stateStr(state) {
14827 if (state & STATE_CANCELLED) {
14828 return 'cancel';
14829 } else if (state & STATE_ENDED) {
14830 return 'end';
14831 } else if (state & STATE_CHANGED) {
14832 return 'move';
14833 } else if (state & STATE_BEGAN) {
14834 return 'start';
14835 }
14836 return '';
14837}
14838
14839/**
14840 * direction cons to string
14841 * @param {Const} direction
14842 * @returns {String}
14843 */
14844function directionStr(direction) {
14845 if (direction == DIRECTION_DOWN) {
14846 return 'down';
14847 } else if (direction == DIRECTION_UP) {
14848 return 'up';
14849 } else if (direction == DIRECTION_LEFT) {
14850 return 'left';
14851 } else if (direction == DIRECTION_RIGHT) {
14852 return 'right';
14853 }
14854 return '';
14855}
14856
14857/**
14858 * get a recognizer by name if it is bound to a manager
14859 * @param {Recognizer|String} otherRecognizer
14860 * @param {Recognizer} recognizer
14861 * @returns {Recognizer}
14862 */
14863function getRecognizerByNameIfManager(otherRecognizer, recognizer) {
14864 var manager = recognizer.manager;
14865 if (manager) {
14866 return manager.get(otherRecognizer);
14867 }
14868 return otherRecognizer;
14869}
14870
14871/**
14872 * This recognizer is just used as a base for the simple attribute recognizers.
14873 * @constructor
14874 * @extends Recognizer
14875 */
14876function AttrRecognizer() {
14877 Recognizer.apply(this, arguments);
14878}
14879
14880inherit(AttrRecognizer, Recognizer, {
14881 /**
14882 * @namespace
14883 * @memberof AttrRecognizer
14884 */
14885 defaults: {
14886 /**
14887 * @type {Number}
14888 * @default 1
14889 */
14890 pointers: 1
14891 },
14892
14893 /**
14894 * Used to check if it the recognizer receives valid input, like input.distance > 10.
14895 * @memberof AttrRecognizer
14896 * @param {Object} input
14897 * @returns {Boolean} recognized
14898 */
14899 attrTest: function(input) {
14900 var optionPointers = this.options.pointers;
14901 return optionPointers === 0 || input.pointers.length === optionPointers;
14902 },
14903
14904 /**
14905 * Process the input and return the state for the recognizer
14906 * @memberof AttrRecognizer
14907 * @param {Object} input
14908 * @returns {*} State
14909 */
14910 process: function(input) {
14911 var state = this.state;
14912 var eventType = input.eventType;
14913
14914 var isRecognized = state & (STATE_BEGAN | STATE_CHANGED);
14915 var isValid = this.attrTest(input);
14916
14917 // on cancel input and we've recognized before, return STATE_CANCELLED
14918 if (isRecognized && (eventType & INPUT_CANCEL || !isValid)) {
14919 return state | STATE_CANCELLED;
14920 } else if (isRecognized || isValid) {
14921 if (eventType & INPUT_END) {
14922 return state | STATE_ENDED;
14923 } else if (!(state & STATE_BEGAN)) {
14924 return STATE_BEGAN;
14925 }
14926 return state | STATE_CHANGED;
14927 }
14928 return STATE_FAILED;
14929 }
14930});
14931
14932/**
14933 * Pan
14934 * Recognized when the pointer is down and moved in the allowed direction.
14935 * @constructor
14936 * @extends AttrRecognizer
14937 */
14938function PanRecognizer() {
14939 AttrRecognizer.apply(this, arguments);
14940
14941 this.pX = null;
14942 this.pY = null;
14943}
14944
14945inherit(PanRecognizer, AttrRecognizer, {
14946 /**
14947 * @namespace
14948 * @memberof PanRecognizer
14949 */
14950 defaults: {
14951 event: 'pan',
14952 threshold: 10,
14953 pointers: 1,
14954 direction: DIRECTION_ALL
14955 },
14956
14957 getTouchAction: function() {
14958 var direction = this.options.direction;
14959 var actions = [];
14960 if (direction & DIRECTION_HORIZONTAL) {
14961 actions.push(TOUCH_ACTION_PAN_Y);
14962 }
14963 if (direction & DIRECTION_VERTICAL) {
14964 actions.push(TOUCH_ACTION_PAN_X);
14965 }
14966 return actions;
14967 },
14968
14969 directionTest: function(input) {
14970 var options = this.options;
14971 var hasMoved = true;
14972 var distance = input.distance;
14973 var direction = input.direction;
14974 var x = input.deltaX;
14975 var y = input.deltaY;
14976
14977 // lock to axis?
14978 if (!(direction & options.direction)) {
14979 if (options.direction & DIRECTION_HORIZONTAL) {
14980 direction = (x === 0) ? DIRECTION_NONE : (x < 0) ? DIRECTION_LEFT : DIRECTION_RIGHT;
14981 hasMoved = x != this.pX;
14982 distance = Math.abs(input.deltaX);
14983 } else {
14984 direction = (y === 0) ? DIRECTION_NONE : (y < 0) ? DIRECTION_UP : DIRECTION_DOWN;
14985 hasMoved = y != this.pY;
14986 distance = Math.abs(input.deltaY);
14987 }
14988 }
14989 input.direction = direction;
14990 return hasMoved && distance > options.threshold && direction & options.direction;
14991 },
14992
14993 attrTest: function(input) {
14994 return AttrRecognizer.prototype.attrTest.call(this, input) &&
14995 (this.state & STATE_BEGAN || (!(this.state & STATE_BEGAN) && this.directionTest(input)));
14996 },
14997
14998 emit: function(input) {
14999
15000 this.pX = input.deltaX;
15001 this.pY = input.deltaY;
15002
15003 var direction = directionStr(input.direction);
15004
15005 if (direction) {
15006 input.additionalEvent = this.options.event + direction;
15007 }
15008 this._super.emit.call(this, input);
15009 }
15010});
15011
15012/**
15013 * Pinch
15014 * Recognized when two or more pointers are moving toward (zoom-in) or away from each other (zoom-out).
15015 * @constructor
15016 * @extends AttrRecognizer
15017 */
15018function PinchRecognizer() {
15019 AttrRecognizer.apply(this, arguments);
15020}
15021
15022inherit(PinchRecognizer, AttrRecognizer, {
15023 /**
15024 * @namespace
15025 * @memberof PinchRecognizer
15026 */
15027 defaults: {
15028 event: 'pinch',
15029 threshold: 0,
15030 pointers: 2
15031 },
15032
15033 getTouchAction: function() {
15034 return [TOUCH_ACTION_NONE];
15035 },
15036
15037 attrTest: function(input) {
15038 return this._super.attrTest.call(this, input) &&
15039 (Math.abs(input.scale - 1) > this.options.threshold || this.state & STATE_BEGAN);
15040 },
15041
15042 emit: function(input) {
15043 if (input.scale !== 1) {
15044 var inOut = input.scale < 1 ? 'in' : 'out';
15045 input.additionalEvent = this.options.event + inOut;
15046 }
15047 this._super.emit.call(this, input);
15048 }
15049});
15050
15051/**
15052 * Press
15053 * Recognized when the pointer is down for x ms without any movement.
15054 * @constructor
15055 * @extends Recognizer
15056 */
15057function PressRecognizer() {
15058 Recognizer.apply(this, arguments);
15059
15060 this._timer = null;
15061 this._input = null;
15062}
15063
15064inherit(PressRecognizer, Recognizer, {
15065 /**
15066 * @namespace
15067 * @memberof PressRecognizer
15068 */
15069 defaults: {
15070 event: 'press',
15071 pointers: 1,
15072 time: 251, // minimal time of the pointer to be pressed
15073 threshold: 9 // a minimal movement is ok, but keep it low
15074 },
15075
15076 getTouchAction: function() {
15077 return [TOUCH_ACTION_AUTO];
15078 },
15079
15080 process: function(input) {
15081 var options = this.options;
15082 var validPointers = input.pointers.length === options.pointers;
15083 var validMovement = input.distance < options.threshold;
15084 var validTime = input.deltaTime > options.time;
15085
15086 this._input = input;
15087
15088 // we only allow little movement
15089 // and we've reached an end event, so a tap is possible
15090 if (!validMovement || !validPointers || (input.eventType & (INPUT_END | INPUT_CANCEL) && !validTime)) {
15091 this.reset();
15092 } else if (input.eventType & INPUT_START) {
15093 this.reset();
15094 this._timer = setTimeoutContext(function() {
15095 this.state = STATE_RECOGNIZED;
15096 this.tryEmit();
15097 }, options.time, this);
15098 } else if (input.eventType & INPUT_END) {
15099 return STATE_RECOGNIZED;
15100 }
15101 return STATE_FAILED;
15102 },
15103
15104 reset: function() {
15105 clearTimeout(this._timer);
15106 },
15107
15108 emit: function(input) {
15109 if (this.state !== STATE_RECOGNIZED) {
15110 return;
15111 }
15112
15113 if (input && (input.eventType & INPUT_END)) {
15114 this.manager.emit(this.options.event + 'up', input);
15115 } else {
15116 this._input.timeStamp = now();
15117 this.manager.emit(this.options.event, this._input);
15118 }
15119 }
15120});
15121
15122/**
15123 * Rotate
15124 * Recognized when two or more pointer are moving in a circular motion.
15125 * @constructor
15126 * @extends AttrRecognizer
15127 */
15128function RotateRecognizer() {
15129 AttrRecognizer.apply(this, arguments);
15130}
15131
15132inherit(RotateRecognizer, AttrRecognizer, {
15133 /**
15134 * @namespace
15135 * @memberof RotateRecognizer
15136 */
15137 defaults: {
15138 event: 'rotate',
15139 threshold: 0,
15140 pointers: 2
15141 },
15142
15143 getTouchAction: function() {
15144 return [TOUCH_ACTION_NONE];
15145 },
15146
15147 attrTest: function(input) {
15148 return this._super.attrTest.call(this, input) &&
15149 (Math.abs(input.rotation) > this.options.threshold || this.state & STATE_BEGAN);
15150 }
15151});
15152
15153/**
15154 * Swipe
15155 * Recognized when the pointer is moving fast (velocity), with enough distance in the allowed direction.
15156 * @constructor
15157 * @extends AttrRecognizer
15158 */
15159function SwipeRecognizer() {
15160 AttrRecognizer.apply(this, arguments);
15161}
15162
15163inherit(SwipeRecognizer, AttrRecognizer, {
15164 /**
15165 * @namespace
15166 * @memberof SwipeRecognizer
15167 */
15168 defaults: {
15169 event: 'swipe',
15170 threshold: 10,
15171 velocity: 0.3,
15172 direction: DIRECTION_HORIZONTAL | DIRECTION_VERTICAL,
15173 pointers: 1
15174 },
15175
15176 getTouchAction: function() {
15177 return PanRecognizer.prototype.getTouchAction.call(this);
15178 },
15179
15180 attrTest: function(input) {
15181 var direction = this.options.direction;
15182 var velocity;
15183
15184 if (direction & (DIRECTION_HORIZONTAL | DIRECTION_VERTICAL)) {
15185 velocity = input.overallVelocity;
15186 } else if (direction & DIRECTION_HORIZONTAL) {
15187 velocity = input.overallVelocityX;
15188 } else if (direction & DIRECTION_VERTICAL) {
15189 velocity = input.overallVelocityY;
15190 }
15191
15192 return this._super.attrTest.call(this, input) &&
15193 direction & input.offsetDirection &&
15194 input.distance > this.options.threshold &&
15195 input.maxPointers == this.options.pointers &&
15196 abs(velocity) > this.options.velocity && input.eventType & INPUT_END;
15197 },
15198
15199 emit: function(input) {
15200 var direction = directionStr(input.offsetDirection);
15201 if (direction) {
15202 this.manager.emit(this.options.event + direction, input);
15203 }
15204
15205 this.manager.emit(this.options.event, input);
15206 }
15207});
15208
15209/**
15210 * A tap is ecognized when the pointer is doing a small tap/click. Multiple taps are recognized if they occur
15211 * between the given interval and position. The delay option can be used to recognize multi-taps without firing
15212 * a single tap.
15213 *
15214 * The eventData from the emitted event contains the property `tapCount`, which contains the amount of
15215 * multi-taps being recognized.
15216 * @constructor
15217 * @extends Recognizer
15218 */
15219function TapRecognizer() {
15220 Recognizer.apply(this, arguments);
15221
15222 // previous time and center,
15223 // used for tap counting
15224 this.pTime = false;
15225 this.pCenter = false;
15226
15227 this._timer = null;
15228 this._input = null;
15229 this.count = 0;
15230}
15231
15232inherit(TapRecognizer, Recognizer, {
15233 /**
15234 * @namespace
15235 * @memberof PinchRecognizer
15236 */
15237 defaults: {
15238 event: 'tap',
15239 pointers: 1,
15240 taps: 1,
15241 interval: 300, // max time between the multi-tap taps
15242 time: 250, // max time of the pointer to be down (like finger on the screen)
15243 threshold: 9, // a minimal movement is ok, but keep it low
15244 posThreshold: 10 // a multi-tap can be a bit off the initial position
15245 },
15246
15247 getTouchAction: function() {
15248 return [TOUCH_ACTION_MANIPULATION];
15249 },
15250
15251 process: function(input) {
15252 var options = this.options;
15253
15254 var validPointers = input.pointers.length === options.pointers;
15255 var validMovement = input.distance < options.threshold;
15256 var validTouchTime = input.deltaTime < options.time;
15257
15258 this.reset();
15259
15260 if ((input.eventType & INPUT_START) && (this.count === 0)) {
15261 return this.failTimeout();
15262 }
15263
15264 // we only allow little movement
15265 // and we've reached an end event, so a tap is possible
15266 if (validMovement && validTouchTime && validPointers) {
15267 if (input.eventType != INPUT_END) {
15268 return this.failTimeout();
15269 }
15270
15271 var validInterval = this.pTime ? (input.timeStamp - this.pTime < options.interval) : true;
15272 var validMultiTap = !this.pCenter || getDistance(this.pCenter, input.center) < options.posThreshold;
15273
15274 this.pTime = input.timeStamp;
15275 this.pCenter = input.center;
15276
15277 if (!validMultiTap || !validInterval) {
15278 this.count = 1;
15279 } else {
15280 this.count += 1;
15281 }
15282
15283 this._input = input;
15284
15285 // if tap count matches we have recognized it,
15286 // else it has began recognizing...
15287 var tapCount = this.count % options.taps;
15288 if (tapCount === 0) {
15289 // no failing requirements, immediately trigger the tap event
15290 // or wait as long as the multitap interval to trigger
15291 if (!this.hasRequireFailures()) {
15292 return STATE_RECOGNIZED;
15293 } else {
15294 this._timer = setTimeoutContext(function() {
15295 this.state = STATE_RECOGNIZED;
15296 this.tryEmit();
15297 }, options.interval, this);
15298 return STATE_BEGAN;
15299 }
15300 }
15301 }
15302 return STATE_FAILED;
15303 },
15304
15305 failTimeout: function() {
15306 this._timer = setTimeoutContext(function() {
15307 this.state = STATE_FAILED;
15308 }, this.options.interval, this);
15309 return STATE_FAILED;
15310 },
15311
15312 reset: function() {
15313 clearTimeout(this._timer);
15314 },
15315
15316 emit: function() {
15317 if (this.state == STATE_RECOGNIZED) {
15318 this._input.tapCount = this.count;
15319 this.manager.emit(this.options.event, this._input);
15320 }
15321 }
15322});
15323
15324/**
15325 * Simple way to create a manager with a default set of recognizers.
15326 * @param {HTMLElement} element
15327 * @param {Object} [options]
15328 * @constructor
15329 */
15330function NGHammer(element, options) {
15331 options = options || {};
15332 options.recognizers = ifUndefined(options.recognizers, NGHammer.defaults.preset);
15333 return new Manager(element, options);
15334}
15335
15336/**
15337 * @const {string}
15338 */
15339NGHammer.VERSION = '2.0.7';
15340
15341/**
15342 * default settings
15343 * @namespace
15344 */
15345NGHammer.defaults = {
15346 /**
15347 * set if DOM events are being triggered.
15348 * But this is slower and unused by simple implementations, so disabled by default.
15349 * @type {Boolean}
15350 * @default false
15351 */
15352 domEvents: false,
15353
15354 /**
15355 * The value for the touchAction property/fallback.
15356 * When set to `compute` it will magically set the correct value based on the added recognizers.
15357 * @type {String}
15358 * @default compute
15359 */
15360 touchAction: TOUCH_ACTION_COMPUTE,
15361
15362 /**
15363 * @type {Boolean}
15364 * @default true
15365 */
15366 enable: true,
15367
15368 /**
15369 * EXPERIMENTAL FEATURE -- can be removed/changed
15370 * Change the parent input target element.
15371 * If Null, then it is being set the to main element.
15372 * @type {Null|EventTarget}
15373 * @default null
15374 */
15375 inputTarget: null,
15376
15377 /**
15378 * force an input class
15379 * @type {Null|Function}
15380 * @default null
15381 */
15382 inputClass: null,
15383
15384 /**
15385 * Default recognizer setup when calling `NGHammer()`
15386 * When creating a new Manager these will be skipped.
15387 * @type {Array}
15388 */
15389 preset: [
15390 // RecognizerClass, options, [recognizeWith, ...], [requireFailure, ...]
15391 [RotateRecognizer, {enable: false}],
15392 [PinchRecognizer, {enable: false}, ['rotate']],
15393 [SwipeRecognizer, {direction: DIRECTION_HORIZONTAL}],
15394 [PanRecognizer, {direction: DIRECTION_HORIZONTAL}, ['swipe']],
15395 [TapRecognizer],
15396 [TapRecognizer, {event: 'doubletap', taps: 2}, ['tap']],
15397 [PressRecognizer]
15398 ],
15399
15400 /**
15401 * Some CSS properties can be used to improve the working of NGHammer.
15402 * Add them to this method and they will be set when creating a new Manager.
15403 * @namespace
15404 */
15405 cssProps: {
15406 /**
15407 * Disables text selection to improve the dragging gesture. Mainly for desktop browsers.
15408 * @type {String}
15409 * @default 'none'
15410 */
15411 userSelect: 'none',
15412
15413 /**
15414 * Disable the Windows Phone grippers when pressing an element.
15415 * @type {String}
15416 * @default 'none'
15417 */
15418 touchSelect: 'none',
15419
15420 /**
15421 * Disables the default callout shown when you touch and hold a touch target.
15422 * On iOS, when you touch and hold a touch target such as a link, Safari displays
15423 * a callout containing information about the link. This property allows you to disable that callout.
15424 * @type {String}
15425 * @default 'none'
15426 */
15427 touchCallout: 'none',
15428
15429 /**
15430 * Specifies whether zooming is enabled. Used by IE10>
15431 * @type {String}
15432 * @default 'none'
15433 */
15434 contentZooming: 'none',
15435
15436 /**
15437 * Specifies that an entire element should be draggable instead of its contents. Mainly for desktop browsers.
15438 * @type {String}
15439 * @default 'none'
15440 */
15441 userDrag: 'none',
15442
15443 /**
15444 * Overrides the highlight color shown when the user taps a link or a JavaScript
15445 * clickable element in iOS. This property obeys the alpha value, if specified.
15446 * @type {String}
15447 * @default 'rgba(0,0,0,0)'
15448 */
15449 tapHighlightColor: 'rgba(0,0,0,0)'
15450 }
15451};
15452
15453var STOP = 1;
15454var FORCED_STOP = 2;
15455
15456/**
15457 * Manager
15458 * @param {HTMLElement} element
15459 * @param {Object} [options]
15460 * @constructor
15461 */
15462function Manager(element, options) {
15463 this.options = assign({}, NGHammer.defaults, options || {});
15464
15465 this.options.inputTarget = this.options.inputTarget || element;
15466
15467 this.handlers = {};
15468 this.session = {};
15469 this.recognizers = [];
15470 this.oldCssProps = {};
15471
15472 this.element = element;
15473 this.input = createInputInstance(this);
15474 this.touchAction = new TouchAction(this, this.options.touchAction);
15475
15476 toggleCssProps(this, true);
15477
15478 each(this.options.recognizers, function(item) {
15479 var recognizer = this.add(new (item[0])(item[1]));
15480 item[2] && recognizer.recognizeWith(item[2]);
15481 item[3] && recognizer.requireFailure(item[3]);
15482 }, this);
15483}
15484
15485Manager.prototype = {
15486 /**
15487 * set options
15488 * @param {Object} options
15489 * @returns {Manager}
15490 */
15491 set: function(options) {
15492 assign(this.options, options);
15493
15494 // Options that need a little more setup
15495 if (options.touchAction) {
15496 this.touchAction.update();
15497 }
15498 if (options.inputTarget) {
15499 // Clean up existing event listeners and reinitialize
15500 this.input.destroy();
15501 this.input.target = options.inputTarget;
15502 this.input.init();
15503 }
15504 return this;
15505 },
15506
15507 /**
15508 * stop recognizing for this session.
15509 * This session will be discarded, when a new [input]start event is fired.
15510 * When forced, the recognizer cycle is stopped immediately.
15511 * @param {Boolean} [force]
15512 */
15513 stop: function(force) {
15514 this.session.stopped = force ? FORCED_STOP : STOP;
15515 },
15516
15517 /**
15518 * run the recognizers!
15519 * called by the inputHandler function on every movement of the pointers (touches)
15520 * it walks through all the recognizers and tries to detect the gesture that is being made
15521 * @param {Object} inputData
15522 */
15523 recognize: function(inputData) {
15524 var session = this.session;
15525 if (session.stopped) {
15526 return;
15527 }
15528
15529 // run the touch-action polyfill
15530 this.touchAction.preventDefaults(inputData);
15531
15532 var recognizer;
15533 var recognizers = this.recognizers;
15534
15535 // this holds the recognizer that is being recognized.
15536 // so the recognizer's state needs to be BEGAN, CHANGED, ENDED or RECOGNIZED
15537 // if no recognizer is detecting a thing, it is set to `null`
15538 var curRecognizer = session.curRecognizer;
15539
15540 // reset when the last recognizer is recognized
15541 // or when we're in a new session
15542 if (!curRecognizer || (curRecognizer && curRecognizer.state & STATE_RECOGNIZED)) {
15543 curRecognizer = session.curRecognizer = null;
15544 }
15545
15546 var i = 0;
15547 while (i < recognizers.length) {
15548 recognizer = recognizers[i];
15549
15550 // find out if we are allowed try to recognize the input for this one.
15551 // 1. allow if the session is NOT forced stopped (see the .stop() method)
15552 // 2. allow if we still haven't recognized a gesture in this session, or the this recognizer is the one
15553 // that is being recognized.
15554 // 3. allow if the recognizer is allowed to run simultaneous with the current recognized recognizer.
15555 // this can be setup with the `recognizeWith()` method on the recognizer.
15556 if (session.stopped !== FORCED_STOP && ( // 1
15557 !curRecognizer || recognizer == curRecognizer || // 2
15558 recognizer.canRecognizeWith(curRecognizer))) { // 3
15559 recognizer.recognize(inputData);
15560 } else {
15561 recognizer.reset();
15562 }
15563
15564 // if the recognizer has been recognizing the input as a valid gesture, we want to store this one as the
15565 // current active recognizer. but only if we don't already have an active recognizer
15566 if (!curRecognizer && recognizer.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED)) {
15567 curRecognizer = session.curRecognizer = recognizer;
15568 }
15569 i++;
15570 }
15571 },
15572
15573 /**
15574 * get a recognizer by its event name.
15575 * @param {Recognizer|String} recognizer
15576 * @returns {Recognizer|Null}
15577 */
15578 get: function(recognizer) {
15579 if (recognizer instanceof Recognizer) {
15580 return recognizer;
15581 }
15582
15583 var recognizers = this.recognizers;
15584 for (var i = 0; i < recognizers.length; i++) {
15585 if (recognizers[i].options.event == recognizer) {
15586 return recognizers[i];
15587 }
15588 }
15589 return null;
15590 },
15591
15592 /**
15593 * add a recognizer to the manager
15594 * existing recognizers with the same event name will be removed
15595 * @param {Recognizer} recognizer
15596 * @returns {Recognizer|Manager}
15597 */
15598 add: function(recognizer) {
15599 if (invokeArrayArg(recognizer, 'add', this)) {
15600 return this;
15601 }
15602
15603 // remove existing
15604 var existing = this.get(recognizer.options.event);
15605 if (existing) {
15606 this.remove(existing);
15607 }
15608
15609 this.recognizers.push(recognizer);
15610 recognizer.manager = this;
15611
15612 this.touchAction.update();
15613 return recognizer;
15614 },
15615
15616 /**
15617 * remove a recognizer by name or instance
15618 * @param {Recognizer|String} recognizer
15619 * @returns {Manager}
15620 */
15621 remove: function(recognizer) {
15622 if (invokeArrayArg(recognizer, 'remove', this)) {
15623 return this;
15624 }
15625
15626 recognizer = this.get(recognizer);
15627
15628 // let's make sure this recognizer exists
15629 if (recognizer) {
15630 var recognizers = this.recognizers;
15631 var index = inArray(recognizers, recognizer);
15632
15633 if (index !== -1) {
15634 recognizers.splice(index, 1);
15635 this.touchAction.update();
15636 }
15637 }
15638
15639 return this;
15640 },
15641
15642 /**
15643 * bind event
15644 * @param {String} events
15645 * @param {Function} handler
15646 * @returns {EventEmitter} this
15647 */
15648 on: function(events, handler) {
15649 if (events === undefined) {
15650 return;
15651 }
15652 if (handler === undefined) {
15653 return;
15654 }
15655
15656 var handlers = this.handlers;
15657 each(splitStr(events), function(event) {
15658 handlers[event] = handlers[event] || [];
15659 handlers[event].push(handler);
15660 });
15661 return this;
15662 },
15663
15664 /**
15665 * unbind event, leave emit blank to remove all handlers
15666 * @param {String} events
15667 * @param {Function} [handler]
15668 * @returns {EventEmitter} this
15669 */
15670 off: function(events, handler) {
15671 if (events === undefined) {
15672 return;
15673 }
15674
15675 var handlers = this.handlers;
15676 each(splitStr(events), function(event) {
15677 if (!handler) {
15678 delete handlers[event];
15679 } else {
15680 handlers[event] && handlers[event].splice(inArray(handlers[event], handler), 1);
15681 }
15682 });
15683 return this;
15684 },
15685
15686 /**
15687 * emit event to the listeners
15688 * @param {String} event
15689 * @param {Object} data
15690 */
15691 emit: function(event, data) {
15692 // we also want to trigger dom events
15693 if (this.options.domEvents) {
15694 triggerDomEvent(event, data);
15695 }
15696
15697 // no handlers, so skip it all
15698 var handlers = this.handlers[event] && this.handlers[event].slice();
15699 if (!handlers || !handlers.length) {
15700 return;
15701 }
15702
15703 data.type = event;
15704 data.preventDefault = function() {
15705 data.srcEvent.preventDefault();
15706 };
15707
15708 var i = 0;
15709 while (i < handlers.length) {
15710 handlers[i](data);
15711 i++;
15712 }
15713 },
15714
15715 /**
15716 * destroy the manager and unbinds all events
15717 * it doesn't unbind dom events, that is the user own responsibility
15718 */
15719 destroy: function() {
15720 this.element && toggleCssProps(this, false);
15721
15722 this.handlers = {};
15723 this.session = {};
15724 this.input.destroy();
15725 this.element = null;
15726 }
15727};
15728
15729/**
15730 * add/remove the css properties as defined in manager.options.cssProps
15731 * @param {Manager} manager
15732 * @param {Boolean} add
15733 */
15734function toggleCssProps(manager, add) {
15735 var element = manager.element;
15736 if (!element.style) {
15737 return;
15738 }
15739 var prop;
15740 each(manager.options.cssProps, function(value, name) {
15741 prop = prefixed(element.style, name);
15742 if (add) {
15743 manager.oldCssProps[prop] = element.style[prop];
15744 element.style[prop] = value;
15745 } else {
15746 element.style[prop] = manager.oldCssProps[prop] || '';
15747 }
15748 });
15749 if (!add) {
15750 manager.oldCssProps = {};
15751 }
15752}
15753
15754/**
15755 * trigger dom event
15756 * @param {String} event
15757 * @param {Object} data
15758 */
15759function triggerDomEvent(event, data) {
15760 var gestureEvent = document.createEvent('Event');
15761 gestureEvent.initEvent(event, true, true);
15762 gestureEvent.gesture = data;
15763 data.target.dispatchEvent(gestureEvent);
15764}
15765
15766assign(NGHammer, {
15767 INPUT_START: INPUT_START,
15768 INPUT_MOVE: INPUT_MOVE,
15769 INPUT_END: INPUT_END,
15770 INPUT_CANCEL: INPUT_CANCEL,
15771
15772 STATE_POSSIBLE: STATE_POSSIBLE,
15773 STATE_BEGAN: STATE_BEGAN,
15774 STATE_CHANGED: STATE_CHANGED,
15775 STATE_ENDED: STATE_ENDED,
15776 STATE_RECOGNIZED: STATE_RECOGNIZED,
15777 STATE_CANCELLED: STATE_CANCELLED,
15778 STATE_FAILED: STATE_FAILED,
15779
15780 DIRECTION_NONE: DIRECTION_NONE,
15781 DIRECTION_LEFT: DIRECTION_LEFT,
15782 DIRECTION_RIGHT: DIRECTION_RIGHT,
15783 DIRECTION_UP: DIRECTION_UP,
15784 DIRECTION_DOWN: DIRECTION_DOWN,
15785 DIRECTION_HORIZONTAL: DIRECTION_HORIZONTAL,
15786 DIRECTION_VERTICAL: DIRECTION_VERTICAL,
15787 DIRECTION_ALL: DIRECTION_ALL,
15788
15789 Manager: Manager,
15790 Input: Input,
15791 TouchAction: TouchAction,
15792
15793 TouchInput: TouchInput,
15794 MouseInput: MouseInput,
15795 PointerEventInput: PointerEventInput,
15796 TouchMouseInput: TouchMouseInput,
15797 SingleTouchInput: SingleTouchInput,
15798
15799 Recognizer: Recognizer,
15800 AttrRecognizer: AttrRecognizer,
15801 Tap: TapRecognizer,
15802 Pan: PanRecognizer,
15803 Swipe: SwipeRecognizer,
15804 Pinch: PinchRecognizer,
15805 Rotate: RotateRecognizer,
15806 Press: PressRecognizer,
15807
15808 on: addEventListeners,
15809 off: removeEventListeners,
15810 each: each,
15811 merge: merge,
15812 extend: extend,
15813 assign: assign,
15814 inherit: inherit,
15815 bindFn: bindFn,
15816 prefixed: prefixed
15817});
15818
15819// this prevents errors when NGHammer is loaded in the presence of an AMD
15820// style loader but by script tag, not by the loader.
15821var freeGlobal = (typeof window !== 'undefined' ? window : (typeof self !== 'undefined' ? self : {})); // jshint ignore:line
15822freeGlobal.NGHammer = NGHammer;
15823
15824if (typeof define === 'function' && define.amdDISABLED) {
15825 define(function() {
15826 return NGHammer;
15827 });
15828} else if (typeof module != 'undefined' && module.exports) {
15829 module.exports = NGHammer;
15830} else {
15831 window[exportName] = NGHammer;
15832}
15833
15834})(window, document, 'NGHammer');
15835
15836
15837
15838
15839
15840
15841// END NANOGALLERY2
15842// }( jQuery )));
15843}));
15844
15845
15846//##########################################################################################################################
15847//##########################################################################################################################
15848//##########################################################################################################################
15849//##########################################################################################################################
15850//##########################################################################################################################
15851
15852// nanogallery2 auto start whithout javascript call
15853(function(){
15854 'use strict';
15855
15856 function document_ready(callback){
15857 // in case the document is already rendered
15858 if (document.readyState!='loading') callback();
15859 // modern browsers
15860 else if (document.addEventListener) document.addEventListener('DOMContentLoaded', callback);
15861 // IE <= 8
15862 else document.attachEvent('onreadystatechange', function(){
15863 if (document.readyState=='complete') callback();
15864 });
15865 }
15866
15867 document_ready(function(){
15868
15869 // retrieve GALLERIES
15870 var t=document.querySelectorAll('[data-nanogallery2]');
15871 for( var i=0; i < t.length; i++ ) {
15872 jQuery( t[i] ).nanogallery2( jQuery(t[i]).data('nanogallery2') );
15873 }
15874
15875 // retrieve SINGLE ELEMENTS -> ONLY LIGHTBOX / NO GALLERY
15876 var t = document.querySelectorAll('[data-nanogallery2-lightbox]');
15877 for( var i=0; i < t.length; i++ ) {
15878
15879 // set mouse pointer
15880 t[i].classList.add('NGY2ThumbnailLightbox');
15881
15882 // add click event
15883 t[i].addEventListener('click', function(e) {
15884 // disable link tag if <A> element
15885 e.preventDefault();
15886
15887 // default options for standalone lightbox
15888 var options = {
15889 lightboxStandalone: true,
15890 viewerToolbar: { display: false }
15891 };
15892
15893 // group of images
15894 var g = this.dataset.nanogallery2Lgroup;
15895
15896 // Retrieve the lightbox configuration
15897 // it just need to be defined on one of the elements, which will be displayed in the lightbox
15898 var t = document.querySelectorAll('[data-nanogallery2-lightbox]');
15899 for( var i=0; i < t.length; i++ ) {
15900 if( t[i].dataset.nanogallery2Lgroup == g ) {
15901 if( t[i].dataset.nanogallery2Lightbox !== "" ) {
15902 options = jQuery.extend(true, {}, options, jQuery(t[i]).data('nanogallery2Lightbox'));
15903 break;
15904 }
15905 }
15906 }
15907 jQuery( this ).nanogallery2( options );
15908
15909 });
15910
15911 }
15912 });
15913
15914
15915
15916 // jQuery(document).ready(function () {
15917
15918 // var t=document.querySelectorAll('[data-nanogallery2-portable]');
15919 // if( t.length > 0 ) {
15920 // portable mode
15921 // var link = document.createElement('link');
15922 // link.setAttribute("rel", "stylesheet");
15923 // link.setAttribute("type", "text/css");
15924 // link.onload = function(){
15925 // for( var i=0; i < t.length; i++ ) {
15926 // jQuery(t[i]).nanogallery2(jQuery(t[i]).data('nanogallery2-portable'));
15927 // }
15928 // }
15929 // link.setAttribute("href", '//nano.gallery/css/nanogallery2.css');
15930 // document.getElementsByTagName("head")[0].appendChild(link);
15931 // }
15932 // else {
15933 // standard mode
15934
15935 // GALLERIES
15936 // var t=document.querySelectorAll('[data-nanogallery2]');
15937 // for( var i=0; i < t.length; i++ ) {
15938 // jQuery( t[i] ).nanogallery2( jQuery(t[i]).data('nanogallery2') );
15939 // }
15940
15941
15942 // }
15943
15944 // });
15945}).call(null);
15946
15947
15948/**!
15949 * @preserve nanogallery2 - NANOPHOTOSPROVIDER2 data provider
15950 * Homepage: http://nanogallery2.nanostudio.org
15951 * Sources: https://github.com/nanostudio-org/nanogallery2
15952 *
15953 * License: GPLv3 and commercial licence
15954 *
15955*/
15956
15957// ########################################################
15958// ##### nanogallery2 - module NANOPHOTOSPROVIDER2 #####
15959// ########################################################
15960
15961
15962(function (factory) {
15963 "use strict";
15964 if (typeof define === 'function' && define.amd) {
15965 // AMD. Register as an anonymous module.
15966 define(['jquery', 'nanogallery2'], factory);
15967 } else if (typeof exports === 'object' && typeof require === 'function') {
15968 // Browserify
15969 factory(require(['jquery', 'nanogallery2']));
15970 } else {
15971 // Browser globals
15972 factory(jQuery);
15973 }
15974}(function ($) {
15975// ;(function ($) {
15976
15977 jQuery.nanogallery2.data_nano_photos_provider2 = function (instance, fnName){
15978 var G = instance; // current nanogallery2 instance
15979
15980 /** @function AlbumGetContent */
15981 var AlbumGetContent = function(albumID, fnToCall, fnParam1, fnParam2) {
15982
15983 var albumIdx = NGY2Item.GetIdx(G, albumID);
15984
15985 // title is identical to ID (only for albums)
15986 if( instance.I[albumIdx].title == '' ) {
15987 instance.I[albumIdx].title = JsonConvertCharset(albumID);
15988 }
15989
15990 // Build the URL
15991 var url = G.O.dataProvider + '?albumID='+albumID; // which album
15992
15993 // all thumbnails sizes (for responsive display)
15994 url += '&hxs=' + G.tn.settings.getH(G.GOM.curNavLevel, 'xs');
15995 url += '&wxs=' + G.tn.settings.getW(G.GOM.curNavLevel, 'xs');
15996 url += '&hsm=' + G.tn.settings.getH(G.GOM.curNavLevel, 'sm');
15997 url += '&wsm=' + G.tn.settings.getW(G.GOM.curNavLevel, 'sm');
15998 url += '&hme=' + G.tn.settings.getH(G.GOM.curNavLevel, 'me');
15999 url += '&wme=' + G.tn.settings.getW(G.GOM.curNavLevel, 'me');
16000 url += '&hla=' + G.tn.settings.getH(G.GOM.curNavLevel, 'la');
16001 url += '&wla=' + G.tn.settings.getW(G.GOM.curNavLevel, 'la');
16002 url += '&hxl=' + G.tn.settings.getH(G.GOM.curNavLevel, 'xl');
16003 url += '&wxl=' + G.tn.settings.getW(G.GOM.curNavLevel, 'xl');
16004 // url += '&wxs=' + G.tn.settings.width[G.GOM.curNavLevel].xs;
16005 // url += '&hxs=' + G.tn.settings.height[G.GOM.curNavLevel].xs;
16006 // url += '&wsm=' + G.tn.settings.width[G.GOM.curNavLevel].sm;
16007 // url += '&hsm=' + G.tn.settings.height[G.GOM.curNavLevel].sm;
16008 // url += '&wme=' + G.tn.settings.width[G.GOM.curNavLevel].me;
16009 // url += '&hme=' + G.tn.settings.height[G.GOM.curNavLevel].me;
16010 // url += '&wla=' + G.tn.settings.width[G.GOM.curNavLevel].la;
16011 // url += '&hla=' + G.tn.settings.height[G.GOM.curNavLevel].la;
16012 // url += '&wxl=' + G.tn.settings.width[G.GOM.curNavLevel].xl;
16013 // url += '&hxl=' + G.tn.settings.height[G.GOM.curNavLevel].xl;
16014
16015 PreloaderDisplay( true );
16016
16017 jQuery.ajaxSetup({ cache: false });
16018 jQuery.support.cors = true;
16019 try {
16020
16021 var tId = setTimeout( function() {
16022 // workaround to handle JSONP (cross-domain) errors
16023 PreloaderDisplay(false);
16024 NanoAlert(G, 'Could not retrieve nanoPhotosProvider2 data (timeout).');
16025 }, 60000 );
16026
16027 if( G.O.debugMode ) { console.log('nanoPhotosProvider2 URL: ' + url); }
16028
16029 jQuery.getJSON(url, function(data, status, xhr) {
16030 clearTimeout( tId );
16031 PreloaderDisplay( false );
16032 JsonParseData(albumIdx, data);
16033
16034 if( data.nano_status == 'ok' ) {
16035 AlbumPostProcess( albumID );
16036 if( fnToCall !== null && fnToCall !== undefined) {
16037 fnToCall( fnParam1, fnParam2, null );
16038 }
16039 }
16040 else {
16041 NanoAlert(G, 'Could not retrieve nanoPhotosProvider2 data. Error: ' + data.nano_status + ' - ' + data.nano_message);
16042 }
16043 })
16044 .fail( function(jqxhr, textStatus, error) {
16045 clearTimeout( tId );
16046 PreloaderDisplay( false );
16047
16048 var k=''
16049 for(var key in jqxhr) {
16050 k+= key + '=' + jqxhr[key] +'<br>';
16051 }
16052 var err = textStatus + ', ' + error + ' ' + k + '<br><br>URL:'+url;
16053 NanoAlert(G, 'Could not retrieve nanoPhotosProvider2 data. Error: ' + err);
16054
16055 });
16056
16057 }
16058 catch(e) {
16059 NanoAlert(G, 'Could not retrieve nanoPhotosProvider2 data. Error: ' + e);
16060 }
16061 }
16062
16063
16064 function JsonConvertCharset( str ) {
16065
16066 return decodeURIComponent(str);
16067
16068
16069 // Pb %C3%A9 --> %E9
16070 // in UTF-8: \u00e9=\xe9 (e9 = hex value)
16071 // switch( G.O.dataCharset.toUpperCase() ) {
16072 // case 'UTF-8': // Apache Windows
16073 // return decodeURI(str); // do not use decodeURIComponent (would convert slash also)
16074 // break;
16075 // case 'Latin': // Apache Linux
16076 // default :
16077 // return escape(str);
16078 // break;
16079 // }
16080 }
16081
16082 function JsonParseData(albumIdx, data) {
16083 if( G.O.debugMode ) {
16084 console.log('nanoPhotosProvider2 parse data:');
16085 console.dir(data);
16086 }
16087
16088
16089 var foundAlbumID = false;
16090 var nb = 0;
16091
16092 // loop each item
16093 jQuery.each( data.album_content, function( i, item ){
16094
16095 // base URL where the images are stored
16096 var baseURL = G.O.dataProvider.substring(0, G.O.dataProvider.indexOf('nano_photos_provider2.php'));
16097
16098 // image URL
16099 var src = baseURL + JsonConvertCharset( item.src );
16100
16101 // item title
16102 var title = item.title;
16103
16104 // item description ( '_' are replaced with ' ' )
16105 var description = item.description.split('_').join(' ');
16106
16107 // item kind ('album' or 'image')
16108 var kind = 'image';
16109 if( item.kind !== undefined && item.kind.length > 0 ) {
16110 kind = item.kind;
16111 }
16112
16113 // item ID
16114 var ID=item.ID;
16115
16116 var filterAlbum = false;
16117 if( kind == 'album' ) {
16118 // check if album name is filtered
16119 if( !FilterAlbumName(title, ID) ) { filterAlbum = true; }
16120 // on gallery initialization : if an album is defined, do not display sub-albums (not supported)
16121 if( G.O.album != '' || G.O.photoset != '' ) { filterAlbum = true; }
16122 }
16123
16124 // if( kind == 'image' || (kind == 'album' && FilterAlbumName(title, ID)) ) {
16125 if( kind == 'image' || !filterAlbum ) {
16126
16127 var albumID = 0;
16128 if( item.albumID !== undefined ) {
16129 albumID = item.albumID;
16130 foundAlbumID = true;
16131 }
16132
16133 var tags = (item.tags === undefined) ? '' : item.tags;
16134 var newItem = NGY2Item.New( G, title.split('_').join(' ') , description, ID, albumID, kind, tags );
16135 newItem.setMediaURL( src, 'img');
16136
16137 // dominant colorS as a gif
16138 if( item.dcGIF !== undefined ) {
16139 newItem.imageDominantColors = 'data:image/gif;base64,' + item.dcGIF;
16140 }
16141 // dominant color as hex rgb value
16142 if( item.dc !== undefined && item.dc !== '' ) {
16143 newItem.imageDominantColor = item.dc;
16144 }
16145
16146 if( kind == 'album' ) {
16147 // number of items in album
16148 newItem.numberItems = item.cnt;
16149 }
16150 else {
16151 // image size
16152 newItem.imageWidth = item.imgWidth;
16153 newItem.imageHeight = item.imgHeight;
16154 }
16155
16156 // item download URL
16157 if( item.originalURL != '' ) {
16158 newItem.downloadURL = baseURL+JsonConvertCharset(item.originalURL);
16159 }
16160
16161 // retrieve responsive thumbnails urls and sizes
16162 var cnl = G.GOM.curNavLevel; // current navigation level ('L1' or 'LN');
16163 var l=['xs', 'sm', 'me', 'la', 'xl'];
16164 for( var n = 0; n < l.length; n++ ) {
16165 newItem.thumbs.url[cnl][l[n]] = baseURL + JsonConvertCharset(item.t_url[n]);
16166 newItem.thumbs.width[cnl][l[n]] = parseInt(item.t_width[n]);
16167 newItem.thumbs.height[cnl][l[n]] = parseInt(item.t_height[n]);
16168 }
16169
16170 // post-process callback
16171 var fu = G.O.fnProcessData;
16172 if( fu !== null ) {
16173 typeof fu == 'function' ? fu(newItem, G.O.dataProvider, data) : window[fu](newItem, G.O.dataProvider, data);
16174 }
16175
16176 }
16177 });
16178
16179 G.I[albumIdx].contentIsLoaded = true; // album's content is ready
16180 }
16181
16182
16183 // -----------
16184 // Initialize
16185 function Init() {
16186
16187 }
16188
16189
16190 // shortcuts to NGY2Tools functions (with context)
16191 var PreloaderDisplay = NGY2Tools.PreloaderDisplay.bind(G);
16192 // var NanoAlert = NGY2Tools.NanoAlert.bind(G);
16193 var NanoAlert = NGY2Tools.NanoAlert;
16194 // var GetImageTitleFromURL = NGY2Tools.GetImageTitleFromURL.bind(G);
16195 var FilterAlbumName = NGY2Tools.FilterAlbumName.bind(G);
16196 var AlbumPostProcess = NGY2Tools.AlbumPostProcess.bind(G);
16197
16198 switch( fnName ){
16199 case 'GetHiddenAlbums':
16200 break;
16201 case 'AlbumGetContent':
16202 var albumID = arguments[2],
16203 callback = arguments[3],
16204 cbParam1 = arguments[4],
16205 cbParam2 = arguments[5];
16206 AlbumGetContent(albumID, callback, cbParam1, cbParam2);
16207 break;
16208 case 'Init':
16209 Init();
16210 break;
16211 case '':
16212 break;
16213 }
16214
16215 };
16216
16217// END NANOPHOTOSPROVIDER DATA SOURCE FOR NANOGALLERY2
16218// }( jQuery ));
16219}));
16220
16221
16222
16223
16224/**!
16225 * @preserve nanogallery2 - GOOGLE PHOTOS data provider
16226 * Homepage: http://nanogallery2.nanostudio.org
16227 * Sources: https://github.com/nanostudio-org/nanogallery2
16228 *
16229 * License: GPLv3 and commercial licence
16230 *
16231*/
16232
16233// ###################################################
16234// ##### nanogallery2 - module for GOOGLE PHOTOS #####
16235// ##### requires nanogp2 #####
16236// ###################################################
16237
16238
16239(function (factory) {
16240 "use strict";
16241 if (typeof define === 'function' && define.amd) {
16242 // AMD. Register as an anonymous module.
16243 define(['jquery', 'nanogallery2'], factory);
16244 } else if (typeof exports === 'object' && typeof require === 'function') {
16245 // Browserify
16246 factory(require(['jquery', 'nanogallery2']));
16247 } else {
16248 // Browser globals
16249 factory(jQuery);
16250 }
16251}(function ($) {
16252// ;(function ($) {
16253
16254 jQuery.nanogallery2.data_google2 = function (instance, fnName){
16255 var G=instance; // current nanogallery2 instance
16256
16257
16258 /** @function AlbumGetContent */
16259 var AlbumGetContent = function(albumID, fnToCall, fnParam1, fnParam2) {
16260
16261 var url = '';
16262 var kind = 'image';
16263 var albumIdx = NGY2Item.GetIdx(G, albumID);
16264
16265 var maxResults='';
16266 if( G.galleryMaxItems.Get() > 0 ) {
16267 maxResults = '&max-results=' + G.galleryMaxItems.Get();
16268 }
16269
16270 var gat=''; // global authorization (using the BUILDER)
16271 if( typeof ngy2_pwa_at !== 'undefined' ) {
16272 gat=ngy2_pwa_at;
16273 }
16274
16275 if( albumID == 0 ) {
16276 // RETRIEVE THE LIST OF ALBUMS
16277 if( gat != '' ) {
16278 // in builder
16279 // url += '?alt=json&v=3&kind=album&deprecation-extension=true&thumbsize='+G.picasa.thumbSizes+maxResults+'&rnd=' + (new Date().getTime()) + '&access_token=' + gat;
16280 url = 'https://photoslibrary.googleapis.com/v1/albums';
16281 }
16282 else {
16283 // NANOGP2
16284 // url=G.O.google2URL + '?nguserid='+G.O.userID+'&alt=json&v=3&kind=album&thumbsize='+G.picasa.thumbSizes+maxResults+'&rnd=' + (new Date().getTime());
16285 url = G.O.google2URL + '?nguserid=' + G.O.userID + '&alt=json&v=3&kind=album' + maxResults + '&rnd=' + (new Date().getTime());
16286 }
16287 kind='album';
16288
16289 }
16290 else {
16291 // RETRIEVE THE CONTENT OF ONE ALBUM (=MEDIAS)
16292 if( gat != '' ) {
16293 // in builder
16294 // url += '/albumid/'+albumID+'?alt=json&kind=photo&deprecation-extension=true&thumbsize='+G.picasa.thumbSizes+maxResults+'&imgmax=d&access_token=' + gat;
16295 // url += '/albumid/'+albumID+'?alt=json&kind=photo&deprecation-extension=true&thumbsize='+G.picasa.thumbSizes+maxResults+'&imgmax=d&access_token=' + gat;
16296 url = 'https://photoslibrary.googleapis.com/v1/mediaItems:search';
16297 }
16298 else {
16299 // nanogp
16300 // url = G.O.google2URL + '?nguserid='+G.O.userID+'&ngalbumid='+albumID+'&alt=json&v=3&kind=photo&thumbsize='+G.picasa.thumbSizes+maxResults+'&imgmax=d';
16301 url = G.O.google2URL + '?nguserid=' + G.O.userID + '&ngalbumid=' + albumID + '&alt=json&v=3&kind=photo&' + maxResults;
16302 }
16303 }
16304
16305 if( G.O.debugMode ) { console.log('Google Photos URL: ' + url); }
16306
16307 PreloaderDisplay(true);
16308 jQuery.ajaxSetup({ cache: false });
16309 jQuery.support.cors = true;
16310 try {
16311 var tId = setTimeout( function() {
16312 // workaround to handle JSONP (cross-domain) errors
16313 PreloaderDisplay(false);
16314 NanoAlert('Could not retrieve AJAX data...');
16315 }, 60000 );
16316
16317 jQuery.getJSON( url + '&callback=?', function(data) {
16318
16319 if( data.nano_status == 'error' ) {
16320 clearTimeout(tId);
16321 PreloaderDisplay(false);
16322 NanoAlert(G, "Could not retrieve Google data. Error: " + data.nano_message);
16323 return;
16324 }
16325 clearTimeout(tId);
16326 PreloaderDisplay(false);
16327 GoogleParseData( albumIdx, kind, data );
16328 AlbumPostProcess(albumID);
16329 if( fnToCall !== null && fnToCall !== undefined) {
16330 fnToCall( fnParam1, fnParam2, null );
16331 }
16332
16333 })
16334 .fail( function(jqxhr, textStatus, error) {
16335 clearTimeout(tId);
16336 PreloaderDisplay(false);
16337
16338 var k=''
16339 for(var key in jqxhr) {
16340 k+= key + '=' + jqxhr[key] +'<br>';
16341 }
16342 var err = textStatus + ', ' + error + ' ' + k + '<br><br>URL:'+url;
16343 NanoAlert(G, "Could not retrieve Google data. Error: " + err);
16344 });
16345 }
16346 catch(e) {
16347 NanoAlert(G, "Could not retrieve Google data. Error: " + e);
16348 }
16349 }
16350
16351
16352 // -----------
16353 // Retrieve items from a Google Photos data stream
16354 // items can be images/viedos or albums
16355 function GoogleParseData(albumIdx, kind, data) {
16356
16357 if( G.O.debugMode ) {
16358 console.log('Google Photos data:');
16359 console.dir(data);
16360 }
16361 var albumID = G.I[albumIdx].GetID();
16362
16363 // iterate and parse each item
16364 jQuery.each(data, function(i,data){
16365
16366 if( typeof data === 'object' && data !== null ) { // only objects
16367
16368 var itemDescription = '';
16369 var itemTitle = '';
16370 if( kind == 'image') {
16371 if (data.description !== undefined ){
16372 itemDescription = data.description
16373 }
16374 if( G.O.thumbnailLabel.get('title') != '' ) {
16375 itemTitle = GetImageTitleFromURL( data.filename );
16376 }
16377 }
16378 else {
16379 itemTitle = data.title;
16380 }
16381 if( itemTitle == undefined ) {
16382 // may happen...
16383 itemTitle = '';
16384 }
16385
16386 var itemID = data.id;
16387 if( kind == 'album' ) {
16388 if( !FilterAlbumName(itemTitle, itemID) || data.coverPhotoBaseUrl == undefined ) {
16389 return true;
16390 }
16391 }
16392
16393 // create ngy2 item
16394 var newItem = NGY2Item.New( G, itemTitle, itemDescription, itemID, albumID, kind, '' );
16395
16396 var width = 0;
16397 var height = 0;
16398
16399 // set the image src
16400 var src = '';
16401 if( kind == 'image' ) {
16402 src = data.baseUrl;
16403 if( !G.O.viewerZoom && G.O.viewerZoom != undefined ) {
16404 if( window.screen.width > window.screen.height ) {
16405 src += '=w' + window.screen.width;
16406 }
16407 else {
16408 src = s + '=h' + window.screen.height;
16409 }
16410 }
16411 else {
16412 // use full resolution image
16413 src += '=h' + data.mediaMetadata.height + '-w' + data.mediaMetadata.width;
16414
16415 // use original image
16416 // src += '=d';
16417 }
16418
16419 // image's URL
16420 newItem.setMediaURL( src, 'img');
16421
16422 // image size
16423 if( data.mediaMetadata.width !== undefined ) {
16424 newItem.imageWidth = parseInt(data.mediaMetadata.width);
16425 width = newItem.imageWidth;
16426 }
16427 if( data.mediaMetadata.height !== undefined ) {
16428 newItem.imageHeight=parseInt(data.mediaMetadata.height);
16429 height = newItem.imageHeight;
16430 }
16431
16432 // if( data.media$group != null && data.media$group.media$credit != null && data.media$group.media$credit.length > 0 ) {
16433 // newItem.author=data.media$group.media$credit[0].$t;
16434 // }
16435
16436 // Photo
16437 if( data.mediaMetadata.photo !== undefined ) {
16438 // exif data
16439 if( data.mediaMetadata.photo.exposureTime != undefined ) {
16440 newItem.exif.exposure = data.mediaMetadata.photo.exposureTime;
16441 }
16442 if( data.mediaMetadata.photo.focalLength != undefined ) {
16443 newItem.exif.focallength = data.mediaMetadata.photo.focalLength;
16444 }
16445 if( data.mediaMetadata.photo.apertureFNumber != undefined ) {
16446 newItem.exif.fstop = data.mediaMetadata.photo.apertureFNumber;
16447 }
16448 if( data.mediaMetadata.photo.isoEquivalent != undefined ) {
16449 newItem.exif.iso = data.mediaMetadata.photo.isoEquivalent;
16450 }
16451 if( data.mediaMetadata.photo.cameraModel != undefined ) {
16452 newItem.exif.model = data.mediaMetadata.photo.cameraModel;
16453 }
16454 }
16455
16456 // Video
16457 if( data.mediaMetadata.video !== undefined ) {
16458 if( data.mediaMetadata.video.cameraModel != undefined ) {
16459 newItem.exif.model = data.mediaMetadata.video.cameraModel;
16460 }
16461
16462 newItem.downloadURL = data.baseUrl + '=dv'; // set the download URL for the video
16463
16464 // newItem.mediaKind = 'selfhosted';
16465 // newItem.mediaMarkup = '<video controls class="nGY2ViewerMedia"><source src="'+ newItem.src +'" type="video/'+ 'video/mp4' +'" preload="auto">Your browser does not support the video tag (HTML 5).</video>';
16466 }
16467
16468 }
16469 else {
16470 // newItem.author = data.author[0].name.$t;
16471 newItem.numberItems = data.mediaItemsCount;
16472 }
16473
16474 // set the URL of the thumbnails images
16475 newItem.thumbs=GoogleThumbSetSizes2('l1', newItem.thumbs, data, kind, height, width );
16476 newItem.thumbs=GoogleThumbSetSizes2('lN', newItem.thumbs, data, kind,height ,width );
16477
16478 // post-process callback
16479 var fu = G.O.fnProcessData;
16480 if( fu !== null ) {
16481 typeof fu == 'function' ? fu(newItem, 'google2', data) : window[fu](newItem, 'google2', data);
16482 }
16483
16484 }
16485 });
16486
16487 G.I[albumIdx].contentIsLoaded = true; // album's content is ready
16488 }
16489
16490 // -----------
16491 // Set thumbnail sizes (width and height) and URLs (for all resolutions (xs, sm, me, la, xl) and levels (l1, lN)
16492 function GoogleThumbSetSizes2(level, tn, data, kind, height, width ) {
16493 var sizes=['xs','sm','me','la','xl'];
16494
16495 for(var i=0; i<sizes.length; i++ ) {
16496
16497 // media
16498 if( kind == 'image' ) {
16499 if( G.tn.settings.width[level][sizes[i]] == 'auto' ) {
16500 let ratio1 = width / height;
16501 tn.height[level][sizes[i]] = G.tn.settings.getH(level, sizes[i]);
16502 tn.width[level][sizes[i]] = G.tn.settings.getH(level, sizes[i]) * ratio1;
16503 tn.url[level][sizes[i]] = data.baseUrl + '=h' + G.tn.settings.getH(level, sizes[i]);
16504 continue;
16505 }
16506 if( G.tn.settings.height[level][sizes[i]] == 'auto' ) {
16507 let ratio1 = height / width;
16508 tn.width[level][sizes[i]] = G.tn.settings.getW(level, sizes[i]);
16509 tn.height[level][sizes[i]] = G.tn.settings.getW(level, sizes[i]) * ratio1;
16510 tn.url[level][sizes[i]] = data.baseUrl + '=w' + G.tn.settings.getW(level, sizes[i]);
16511 continue;
16512 }
16513
16514 tn.height[level][sizes[i]] = G.tn.settings.getH(level, sizes[i]);
16515 tn.width[level][sizes[i]] = G.tn.settings.getW(level, sizes[i]);
16516 tn.url[level][sizes[i]] = data.baseUrl + '=w' + G.tn.settings.getW(level, sizes[i]);
16517
16518 }
16519
16520 // album
16521 if( kind == 'album' ) {
16522 if( G.tn.settings.width[level][sizes[i]] == 'auto' ) {
16523 tn.url[level][sizes[i]]= data.coverPhotoBaseUrl + '=h' + G.tn.settings.getH(level, sizes[i]);
16524 continue;
16525 }
16526 if( G.tn.settings.height[level][sizes[i]] == 'auto' ) {
16527 tn.url[level][sizes[i]]= data.coverPhotoBaseUrl + '=w' + G.tn.settings.getW(level, sizes[i]);
16528 continue;
16529 }
16530 // var w = G.tn.settings.mosaic[level + 'Factor']['w'][sizes[i]];
16531 tn.url[level][sizes[i]]= data.coverPhotoBaseUrl + '=h' + G.tn.settings.getH(level, sizes[i]) + '-w' + G.tn.settings.getW(level, sizes[i]);
16532
16533 }
16534 }
16535
16536 return tn;
16537 }
16538
16539
16540
16541 // -----------
16542 // Initialization
16543 function Init() {
16544 }
16545
16546
16547 // shortcuts to NGY2Tools functions (with context)
16548 var PreloaderDisplay = NGY2Tools.PreloaderDisplay.bind(G);
16549 // var NanoAlert = NGY2Tools.NanoAlert.bind(G);
16550 var NanoAlert = NGY2Tools.NanoAlert;
16551 var GetImageTitleFromURL = NGY2Tools.GetImageTitleFromURL.bind(G);
16552 var FilterAlbumName = NGY2Tools.FilterAlbumName.bind(G);
16553 var AlbumPostProcess = NGY2Tools.AlbumPostProcess.bind(G);
16554
16555 switch( fnName ){
16556 case 'AlbumGetContent':
16557 var albumID = arguments[2],
16558 callback2 = arguments[3],
16559 cbParam1 = arguments[4],
16560 cbParam2 = arguments[5];
16561 AlbumGetContent(albumID, callback2, cbParam1, cbParam2);
16562 break;
16563 case 'Init':
16564 Init();
16565 break;
16566 case '':
16567 break;
16568 }
16569
16570 };
16571
16572// END GOOGLE DATA SOURCE FOR NANOGALLERY2
16573// }( jQuery ));
16574}));
16575
16576
16577
16578
16579/**!
16580 * @preserve nanogallery2 - FLICKR data provider
16581 * Homepage: http://nanogallery2.nanostudio.org
16582 * Sources: https://github.com/nanostudio-org/nanogallery2
16583 *
16584 * License: GPLv3 and commercial licence
16585 *
16586*/
16587
16588// ############################################
16589// ##### nanogallery2 - module for FLICKR #####
16590// ############################################
16591
16592
16593(function (factory) {
16594 "use strict";
16595 if (typeof define === 'function' && define.amd) {
16596 // AMD. Register as an anonymous module.
16597 define(['jquery', 'nanogallery2'], factory);
16598 } else if (typeof exports === 'object' && typeof require === 'function') {
16599 // Browserify
16600 factory(require(['jquery', 'nanogallery2']));
16601 } else {
16602 // Browser globals
16603 factory(jQuery);
16604 }
16605}(function ($) {
16606// ;(function ($) {
16607
16608 jQuery.nanogallery2.data_flickr = function (instance, fnName){
16609 var G = instance; // current nanogallery2 instance
16610
16611 // ### Flickr
16612 // Details: http://www.flickr.com/services/api/misc.urls.html
16613 var Flickr = {
16614 url: function() {
16615 // Flickr API Going SSL-Only on June 27th, 2014
16616 return 'https://api.flickr.com/services/rest/';
16617 },
16618 thumbSize:' sq',
16619 thumbAvailableSizes : new Array(75, 100, 150, 240, 500, 640),
16620 thumbAvailableSizesStr : new Array('sq', 't', 'q', 's', 'm', 'z'),
16621 photoSize : '0',
16622 photoAvailableSizes : new Array(75, 100, 150, 240, 500, 640, 1024, 1024, 1600, 2048, 10000),
16623 photoAvailableSizesStr : new Array('sq', 't', 'q', 's', 'm', 'z', 'b', 'l', 'h', 'k', 'o')
16624 };
16625
16626
16627 /** @function AlbumGetContent */
16628 var AlbumGetContent = function(albumID, fnToCall, fnParam1, fnParam2) {
16629 if( G.O.flickrAPIKey == '' ) {
16630 NanoAlert(G, 'Please set your Flickr API Key (option flickrAPIKey)');
16631 }
16632
16633 var albumIdx = NGY2Item.GetIdx(G, albumID);
16634 var url = '';
16635 var kind = 'image';
16636 // photos
16637 if( G.O.photoset.toUpperCase() == 'NONE' || G.O.album.toUpperCase() == 'NONE' ) {
16638 // get photos from full photostream
16639 url = Flickr.url() + "?&method=flickr.people.getPublicPhotos&api_key=" + G.O.flickrAPIKey + "&user_id="+G.O.userID+"&extras=description,views,tags,url_o,url_sq,url_t,url_q,url_s,url_m,url_z,url_b,url_h,url_k&per_page=500&format=json";
16640 }
16641 else
16642 if( G.I[albumIdx].GetID() == 0 ) {
16643 // retrieve the list of albums
16644 url = Flickr.url() + "?&method=flickr.photosets.getList&api_key=" + G.O.flickrAPIKey + "&user_id="+G.O.userID+"&per_page=500&primary_photo_extras=tags,url_o,url_sq,url_t,url_q,url_s,url_m,url_l,url_z,url_b,url_h,url_k&format=json";
16645 kind='album';
16646 }
16647 else {
16648 // photos from one specific photoset
16649 url = Flickr.url() + "?&method=flickr.photosets.getPhotos&api_key=" + G.O.flickrAPIKey + "&photoset_id="+G.I[albumIdx].GetID()+"&extras=description,views,tags,url_o,url_sq,url_t,url_q,url_s,url_m,url_l,url_z,url_b,url_h,url_k&format=json";
16650 }
16651
16652 if( G.O.debugMode ) { console.log('Flickr URL: ' + url); }
16653
16654 PreloaderDisplay(true);
16655 jQuery.ajaxSetup({ cache: false });
16656 jQuery.support.cors = true;
16657
16658 var tId = setTimeout( function() {
16659 // workaround to handle JSONP (cross-domain) errors
16660 PreloaderDisplay(false);
16661 NanoAlert(G, 'Could not retrieve AJAX data...');
16662 }, 60000 );
16663
16664 var sourceData=[];
16665
16666 // Process the downloaded data
16667 var FlickrGetDone = function() {
16668 clearTimeout(tId);
16669 PreloaderDisplay(false);
16670
16671 // go through sourceData, and exclude blacklisted tags
16672 sourceData = FilterByTags(sourceData, G.O.tagBlockList);
16673
16674 if( kind == 'album' ) {
16675 FlickrParsePhotoSets(albumIdx, albumID, sourceData);
16676 }
16677 else {
16678 FlickrParsePhotos(albumIdx, albumID, sourceData);
16679 }
16680
16681 AlbumPostProcess( albumID );
16682
16683 if( fnToCall !== null && fnToCall !== undefined) {
16684 fnToCall( fnParam1, fnParam2, null );
16685 }
16686 }
16687
16688 // download one page of data (=500 entries)
16689 var FlickrGetOnePage = function( url, page ) {
16690 jQuery.getJSON( url + '&page=' + page + '&jsoncallback=?', function(data, status, xhr) {
16691
16692 var pages=0;
16693 if( kind == 'album' ) {
16694 if( data.stat !== undefined && data.stat === 'fail' ) {
16695 NanoAlert(G, "Could not retrieve Flickr album list: " + data.message + " (code: "+data.code+").");
16696 return false;
16697 }
16698 sourceData=sourceData.concat(data.photosets.photoset);
16699 pages=data.photosets.pages;
16700 }
16701 else {
16702 if( G.O.photoset.toUpperCase() == 'NONE' || G.O.album.toUpperCase() == 'NONE' ) {
16703 // content of full photoset
16704 sourceData=sourceData.concat(data.photos.photo);
16705 pages=data.photos.pages;
16706 }
16707 else {
16708 // content of one album
16709 if( data.stat !== undefined && data.stat === 'fail' ) {
16710 NanoAlert(G, "Could not retrieve Flickr album: " + data.message + " (code: "+data.code+").");
16711 return false;
16712 }
16713 if( G.I[albumIdx].title == '' ) {
16714 G.I[albumIdx].title=data.photoset.title;
16715 }
16716 sourceData=sourceData.concat(data.photoset.photo);
16717 pages=data.photoset.pages;
16718 }
16719
16720 }
16721
16722 if( pages > page ) {
16723 FlickrGetOnePage(url, page+1);
16724 }
16725 else {
16726 FlickrGetDone();
16727 }
16728 })
16729 .fail( function(jqxhr, textStatus, error) {
16730 clearTimeout(tId);
16731 PreloaderDisplay(false);
16732 NanoAlert(G, "Could not retrieve Flickr ajax data: " + textStatus + ', ' + error);
16733 });
16734
16735 }
16736
16737 FlickrGetOnePage(url, 1);
16738
16739 }
16740
16741
16742
16743 // -----------
16744 // Retrieve items for one Flickr photoset
16745 function FlickrParsePhotos( albumIdx, albumID, source ) {
16746
16747 if( G.O.debugMode ) {
16748 console.log('Flickr parse photos:');
16749 console.dir(source);
16750 }
16751
16752 jQuery.each(source, function(i,item){
16753
16754 var itemID = item.id;
16755
16756 var imgUrl=item.url_sq; //fallback size
16757
16758 // get the title
16759 var itemTitle = item.title;
16760 if( G.O.thumbnailLabel.get('title') != '' ) {
16761 itemTitle=GetImageTitleFromURL(imgUrl);
16762 }
16763
16764 // get the description
16765 var itemDescription=item.description._content;
16766
16767 // retrieve the image size with highest available resolution
16768 var imgW=75, imgH=75;
16769 var start=Flickr.photoAvailableSizesStr.length-1;
16770 if( G.O.flickrSkipOriginal ) { start--; }
16771 for( var i = start; i>=0 ; i-- ) {
16772 if( item['url_'+Flickr.photoAvailableSizesStr[i]] != undefined ) {
16773 imgUrl=item['url_'+Flickr.photoAvailableSizesStr[i]];
16774 imgW=parseInt(item['width_'+Flickr.photoAvailableSizesStr[i]]);
16775 imgH=parseInt(item['height_'+Flickr.photoAvailableSizesStr[i]]);
16776 break;
16777 }
16778 }
16779
16780 var sizes = {};
16781 for( var p in item ) {
16782 if( p.indexOf('height_') == 0 || p.indexOf('width_') == 0 || p.indexOf('url_') == 0 ) {
16783 sizes[p]=item[p];
16784 }
16785 }
16786
16787 // tags
16788 var tags = item.tags !== undefined ? item.tags : '';
16789
16790 // create item
16791 var newItem = NGY2Item.New( G, itemTitle, itemDescription, itemID, albumID, 'image', tags );
16792
16793 // add image
16794 newItem.setMediaURL( imgUrl, 'img');
16795 newItem.imageWidth = imgW;
16796 newItem.imageHeight = imgH;
16797
16798
16799 // add thumbnails
16800 var tn = {
16801 url: { l1 : { xs:'', sm:'', me:'', la:'', xl:'' }, lN : { xs:'', sm:'', me:'', la:'', xl:'' } },
16802 width: { l1 : { xs:0, sm:0, me:0, la:0, xl:0 }, lN : { xs:0, sm:0, me:0, la:0, xl:0 } },
16803 height: { l1 : { xs:0, sm:0, me:0, la:0, xl:0 }, lN : { xs:0, sm:0, me:0, la:0, xl:0 } }
16804 };
16805 tn = FlickrRetrieveImages(tn, item, 'l1' );
16806 tn = FlickrRetrieveImages(tn, item, 'lN' );
16807 newItem.thumbs=tn;
16808
16809 // post-process callback
16810 var fu = G.O.fnProcessData;
16811 if( fu !== null ) {
16812 typeof fu == 'function' ? fu(newItem, 'flickr', item) : window[fu](newItem, 'flickr', item);
16813 }
16814
16815
16816 });
16817 G.I[albumIdx].contentIsLoaded=true;
16818
16819 }
16820
16821
16822
16823 // -----------
16824 // Retrieve the list of Flickr photosets
16825 function FlickrParsePhotoSets( albumIdx, albumID, source ) {
16826
16827 if( G.O.debugMode ) {
16828 console.log('Flickr parse list of albums:');
16829 console.dir(source);
16830 }
16831
16832 jQuery.each(source, function(i,item){
16833 //Get the title
16834 var itemTitle = item.title._content;
16835
16836 if( item.visibility_can_see_set == 0 ) { return true; } // skip it
16837
16838 if( FilterAlbumName(itemTitle, item.id) ) {
16839 var itemID=item.id;
16840 //Get the description
16841 var itemDescription = item.description._content != undefined ? item.description._content : '';
16842
16843 var sizes = {};
16844 for( var p in item.primary_photo_extras) {
16845 sizes[p] = item.primary_photo_extras[p];
16846 }
16847 var tags='';
16848 if( item.primary_photo_extras !== undefined ) {
16849 if( item.primary_photo_extras.tags !== undefined ) {
16850 tags = item.primary_photo_extras.tags;
16851 }
16852 }
16853
16854 var newItem = NGY2Item.New( G, itemTitle, itemDescription, itemID, albumID, 'album', tags );
16855 newItem.numberItems = item.photos;
16856 newItem.thumbSizes = sizes;
16857
16858 var tn = {
16859 url: { l1 : { xs:'', sm:'', me:'', la:'', xl:'' }, lN : { xs:'', sm:'', me:'', la:'', xl:'' } },
16860 width: { l1 : { xs:0, sm:0, me:0, la:0, xl:0 }, lN : { xs:0, sm:0, me:0, la:0, xl:0 } },
16861 height: { l1 : { xs:0, sm:0, me:0, la:0, xl:0 }, lN : { xs:0, sm:0, me:0, la:0, xl:0 } }
16862 };
16863 tn = FlickrRetrieveImages(tn, item.primary_photo_extras, 'l1' );
16864 tn = FlickrRetrieveImages(tn, item.primary_photo_extras, 'lN' );
16865 newItem.thumbs = tn;
16866
16867 // post-process callback
16868 var fu = G.O.fnProcessData;
16869 if( fu !== null ) {
16870 typeof fu == 'function' ? fu(newItem, 'flickr', item) : window[fu](newItem, 'flickr', item);
16871 }
16872
16873 }
16874 });
16875
16876 G.I[albumIdx].contentIsLoaded=true;
16877 }
16878
16879 function FlickrRetrieveImages(tn, item, level ) {
16880
16881 var sf=1;
16882 if( G.tn.opt[level].crop === true ) {
16883 sf=G.O.thumbnailCropScaleFactor;
16884 }
16885
16886
16887 var sizes=['xs','sm','me','la','xl'];
16888 for( var i=0; i<sizes.length; i++ ) {
16889 if( G.tn.settings.width[level][sizes[i]] == 'auto' || G.tn.settings.width[level][sizes[i]] == '' ) {
16890 let sdir='height_';
16891 let tsize=Math.ceil( G.tn.settings.height[level][sizes[i]] * G.tn.scale * sf * G.tn.settings.mosaic[level+'Factor']['h'][sizes[i]] );
16892 let one=FlickrRetrieveOneImage(sdir, tsize, item );
16893 tn.url[level][sizes[i]]=one.url;
16894 tn.width[level][sizes[i]]=one.width;
16895 tn.height[level][sizes[i]]=one.height;
16896 }
16897 else
16898 if( G.tn.settings.height[level][sizes[i]] == 'auto' || G.tn.settings.height[level][sizes[i]] == '' ) {
16899 let sdir='width_';
16900 let tsize=Math.ceil( G.tn.settings.width[level][sizes[i]] * G.tn.scale * sf * G.tn.settings.mosaic[level+'Factor']['w'][sizes[i]] );
16901 let one=FlickrRetrieveOneImage(sdir, tsize, item );
16902 tn.url[level][sizes[i]]=one.url;
16903 tn.width[level][sizes[i]]=one.width;
16904 tn.height[level][sizes[i]]=one.height;
16905 }
16906 else {
16907 let sdir='height_';
16908 let tsize=Math.ceil( G.tn.settings.height[level][sizes[i]] * G.tn.scale * sf * G.tn.settings.mosaic[level+'Factor']['h'][sizes[i]] );
16909 if( G.tn.settings.width[level][sizes[i]] > G.tn.settings.height[level][sizes[i]] ) {
16910 sdir='width_';
16911 tsize=Math.ceil( G.tn.settings.width[level][sizes[i]] * G.tn.scale * sf * G.tn.settings.mosaic[level+'Factor']['w'][sizes[i]] );
16912 }
16913 let one=FlickrRetrieveOneImage(sdir, tsize, item );
16914 tn.url[level][sizes[i]]=one.url;
16915 tn.width[level][sizes[i]]=one.width;
16916 tn.height[level][sizes[i]]=one.height;
16917 }
16918 }
16919 return tn;
16920 }
16921
16922 function FlickrRetrieveOneImage(sdir, tsize, item ) {
16923 var one={ url: '', width: 0, height: 0 };
16924 var tnIndex=0;
16925 for( var j=0; j < Flickr.thumbAvailableSizes.length; j++ ) {
16926 var size=item[sdir+Flickr.photoAvailableSizesStr[j]];
16927 if( size != undefined ) {
16928 tnIndex=j;
16929 if( size >= tsize ) {
16930 break;
16931 }
16932 }
16933 }
16934 var fSize=Flickr.photoAvailableSizesStr[tnIndex];
16935 one.url = item['url_'+fSize];
16936 one.width = parseInt(item['width_'+fSize]);
16937 one.height = parseInt(item['height_'+fSize]);
16938 return one;
16939 }
16940
16941 var FilterByTags = function(data, tagBlockList) {
16942 if( tagBlockList!= '' && data != undefined) {
16943 data = data.filter(function (item) {
16944 var regex = new RegExp( tagBlockList, "i");
16945 var tagsToTest = [item.tags];
16946 if ( Array.isArray(item.tags) ) {
16947 tagsToTest = item.tags;
16948 }
16949 return ! tagsToTest.some( function (x) { return regex.test(x); } );
16950 });
16951 }
16952 return data;
16953 };
16954
16955 /** @function GetHiddenAlbums */
16956 // var GetHiddenAlbums = function( hiddenAlbums, callback ){
16957 // not supported -> doesn't exit in Flickr
16958 // callback();
16959 // }
16960
16961 // -----------
16962 // Initialize thumbnail sizes
16963 function Init() {
16964 return;
16965 }
16966
16967
16968 // shortcuts to NGY2Tools functions (with context)
16969 var PreloaderDisplay = NGY2Tools.PreloaderDisplay.bind(G);
16970 var NanoAlert = NGY2Tools.NanoAlert;
16971 var GetImageTitleFromURL = NGY2Tools.GetImageTitleFromURL.bind(G);
16972 var FilterAlbumName = NGY2Tools.FilterAlbumName.bind(G);
16973 var AlbumPostProcess = NGY2Tools.AlbumPostProcess.bind(G);
16974
16975 switch( fnName ){
16976 // case 'GetHiddenAlbums':
16977 // var hiddenAlbums = arguments[2],
16978 // callback = arguments[3];
16979 // GetHiddenAlbums(hiddenAlbums, callback);
16980 // break;
16981 case 'AlbumGetContent':
16982 var albumID = arguments[2],
16983 callback = arguments[3],
16984 cbParam1 = arguments[4],
16985 cbParam2 = arguments[5];
16986 AlbumGetContent(albumID, callback, cbParam1, cbParam2);
16987 break;
16988 case 'Init':
16989 Init();
16990 break;
16991 case '':
16992 break;
16993 }
16994
16995 };
16996
16997// END FLICKR DATA SOURCE FOR NANOGALLERY2
16998// }( jQuery ));
16999}));
17000
17001
17002