UNPKG

586 kBJavaScriptView Raw
1/*!
2 * @preserve nanogallery2 - javascript photo / video gallery and lightbox
3 * Homepage: http://nanogallery2.nanostudio.org
4 * Sources: https://github.com/nanostudio-org/nanogallery2
5 *
6 * License: GPLv3 and commercial licence
7 *
8 * Requirements:
9 * - jQuery (http://www.jquery.com) - version >= 1.7.1
10 *
11 * Embeded components:
12 * - shifty (https://github.com/jeremyckahn/shifty)
13 * - imagesloaded (https://github.com/desandro/imagesloaded)
14 * - hammer.js (http://hammerjs.github.io/)
15 * - screenfull.js (https://github.com/sindresorhus/screenfull.js)
16 * Tools:
17 * - webfont generated with http://fontello.com - mainly based on Font Awesome Copyright (C) 2012 by Dave Gandy (http://fontawesome.io/)
18 * - ICO online converter: https://iconverticons.com/online/
19 */
20
21
22
23// ###########################################
24// ##### nanogallery2 as a JQUERY PLUGIN #####
25// ###########################################
26
27
28
29// Expose plugin as an AMD module if AMD loader is present:
30(function (factory) {
31 "use strict";
32 if (typeof define === 'function' && define.amd) {
33 // AMD. Register as an anonymous module.
34 // define('nanogallery2', ['jquery'], factory);
35 define(['jquery'], factory);
36 } else if (typeof exports === 'object' && typeof require === 'function') {
37 // Browserify
38 factory(require('jquery'));
39 } else {
40 // Browser globals
41 factory(jQuery);
42 }
43}(function ($) {
44// ;(function ($) {
45 "use strict";
46
47 //##### TOOLS/HELPERS ####
48
49 // Convert color to RGB/RGBA
50 function ColorHelperToRGB( color ) {
51 var obj = document.getElementById('ngyColorHelperToRGB');
52 if (obj === null) {
53 obj = document.createElement('div');
54 obj.id = "ngyColorHelperToRGB";
55 obj.style.cssText = 'display: none; color:' + color + ';';
56 document.body.appendChild(obj);
57 }
58
59 var rgb = getComputedStyle(obj).color;
60
61 // to get HEX value:
62 // var rgb = getComputedStyle(obj).color.match(/\d+/g);
63 // var r = parseInt(rgb[0]).toString(16);
64 // var g = parseInt(rgb[1]).toString(16);
65 // var b = parseInt(rgb[2]).toString(16);
66 // var hex = '#' + r + g + b;
67
68 return rgb;
69 }
70
71
72 // ##### helper for color handling
73 // - normalise RGB/RGBA/HEX format
74 // - lighten/darken color
75 // Inspired by:
76 // https://stackoverflow.com/questions/5560248/programmatically-lighten-or-darken-a-hex-color-or-rgb-and-blend-colors
77 // http://www.pimptrizkit.com/?t=20%20Shades
78 function ShadeBlendConvert (p, from, to) {
79 var rgba='';
80 if( from.toUpperCase().substring(0,5) == 'RGBA(' ) {
81 rgba='a';
82 from='rgb('+from.substring(5);
83 }
84
85 if(typeof(p)!="number"||p<-1||p>1||typeof(from)!="string"||(from[0]!='r'&&from[0]!='#')||(typeof(to)!="string"&&typeof(to)!="undefined"))return null;
86 //if(!this.sbcRip)this.sbcRip=function(d){
87 function sbcRip(d){
88 var l=d.length,RGB=new Object();
89 if(l>9){
90 d=d.split(",");
91 if(d.length<3||d.length>4)return null;
92 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;
93 }else{
94 if(l==8||l==6||l<4)return null;
95 if(l<6)d="#"+d[1]+d[1]+d[2]+d[2]+d[3]+d[3]+(l>4?d[4]+""+d[4]:"");
96 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;
97 }
98 return RGB;
99 }
100 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);
101 if(!f||!t)return null;
102 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])+")");
103 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);
104 }
105
106
107 // ##### clone a javascript object
108 function cloneJSObject( obj ) {
109 if (obj === null || typeof obj !== 'object') {
110 return obj;
111 }
112
113 var temp = obj.constructor(); // give temp the original obj's constructor
114 for (var key in obj) {
115 temp[key] = cloneJSObject(obj[key]);
116 }
117 return temp;
118 }
119
120 // get viewport coordinates and size
121 function getViewport() {
122 var $win = jQuery(window);
123 return {
124 l: $win.scrollLeft(),
125 t: $win.scrollTop(),
126 w: $win.width(),
127 h: $win.height()
128 }
129 }
130
131
132 // Check if element is in viewport
133 // avoid if possible (performance issue)
134 /*
135 function inViewport( $elt, threshold ) {
136 var wp = getViewport(),
137 eltOS = $elt.offset(),
138 th = $elt.outerHeight(true),
139 tw = $elt.outerWidth(true);
140 if( eltOS.top >= (wp.t - threshold)
141 && (eltOS.top + th) <= (wp.t + wp.h + threshold)
142 && eltOS.left >= (wp.l - threshold)
143 && (eltOS.left + tw) <= (wp.l + wp.w + threshold) ) {
144 return true;
145 }
146 else {
147 return false;
148 }
149 }
150 */
151
152
153 // Check if whole element is in ViewPort
154 // avoid if possible (performance issue)
155 function inViewportVert( $elt, threshold ) {
156 var wp = getViewport(),
157 eltOS = $elt.offset(),
158 th = $elt.outerHeight(true);
159 //var tw=$elt.outerWidth(true);
160
161 if( wp.t == 0 && (eltOS.top) <= (wp.t + wp.h ) ) { return true; }
162
163 if( eltOS.top >= wp.t && (eltOS.top + th) <= (wp.t + wp.h - threshold) ) {
164 return true;
165 }
166 else {
167 return false;
168 }
169 }
170 // Check if top of the element is in ViewPort
171 function topInViewportVert( $elt, threshold ) {
172 var wp = getViewport(),
173 eltOS = $elt.offset();
174
175 if( eltOS.top >= wp.t && eltOS.top <= (wp.t + wp.h - threshold) ) {
176 return true;
177 }
178 else {
179 return false;
180 }
181 }
182
183
184 // set z-index to display 2 elements on top of all others
185 // function set2ElementsOnTop( start, elt1, elt2 ) {
186 // var highest_index = 0;
187 // if( start=='' ) { start= '*'; }
188 // jQuery(start).each(function() {
189 // var cur = parseInt(jQuery(this).css('z-index'));
190 // highest_index = cur > highest_index ? cur : highest_index;
191 // });
192 // highest_index++;
193 // jQuery(elt2).css('z-index',highest_index+1);
194 // jQuery(elt1).css('z-index',highest_index);
195 // }
196
197 // set z-index to display element on top of all others
198 function setElementOnTop( start, elt ) {
199 var highest_index = 0;
200 if( start == '' ) { start = '*'; }
201 jQuery(start).each(function() {
202 var cur = parseInt(jQuery(this).css('z-index'));
203 highest_index = cur > highest_index ? cur : highest_index;
204 });
205 highest_index++;
206 jQuery(elt).css('z-index',highest_index);
207 }
208
209 // return the real type of the object
210 var toType = function( obj ) {
211 // by Angus Croll - http://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator/
212 return ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase()
213 };
214
215
216 $.nanogallery2 = function (elt, options) {
217 // To avoid scope issues, use '_this' instead of 'this'
218 // to reference this class from internal events and functions.
219 var _this = this;
220
221 // Access to jQuery and DOM versions of element
222 _this.$e = jQuery(elt);
223 _this.e = elt;
224
225 // Add a reverse reference to the DOM object
226 _this.$e.data('nanogallery2data', _this);
227
228 _this.init = function () {
229
230 // define these global objects only once per HTML page
231 if (typeof window.NGY2Item === 'undefined') {
232
233 window.NGY2Tools = (function () {
234
235 function NGY2Tools() {
236 var nextId = 1; // private static --> all instances
237 }
238
239 // check album name - albumList/blockList/allowList
240 NGY2Tools.FilterAlbumName = function( title, ID ) {
241 var s = title.toUpperCase();
242 if( this.albumList.length > 0 ) {
243 for( var j=0; j < this.albumList.length; j++) {
244 if( s === this.albumList[j].toUpperCase() || ID === this.albumList[j] ) {
245 return true;
246 }
247 }
248 }
249 else {
250 var found = false;
251 if( this.allowList !== null ) {
252 //allowList : authorize only album cointaining one of the specified keyword in the title
253 for( var j = 0; j < this.allowList.length; j++) {
254 if( s.indexOf(this.allowList[j]) !== -1 ) {
255 found = true;
256 }
257 }
258 if( !found ) { return false; }
259 }
260
261
262 if( this.blockList !== null ) {
263 //blockList : ignore album cointaining one of the specified keyword in the title
264 for( var j = 0; j < this.blockList.length; j++) {
265 if( s.indexOf(this.blockList[j]) !== -1 ) {
266 return false;
267 }
268 }
269 }
270 return true;
271 }
272 };
273
274
275 /** @function nanoAlert */
276 /* Display an alert message in a specific element */
277 NGY2Tools.NanoAlert = function(context, msg, verbose) {
278 NGY2Tools.NanoConsoleLog.call(context, msg);
279 if( context.$E.conConsole != null ) {
280 context.$E.conConsole.css({visibility: 'visible', minHeight: '100px'});
281 if( verbose == false ) {
282 context.$E.conConsole.append('<p>' + msg + '</p>');
283 }
284 else {
285 context.$E.conConsole.append('<p>nanogallery2: '+ msg + ' [' + context.baseEltID + ']</p>');
286 }
287 }
288 };
289
290
291 /** @function NanoConsoleLog */
292 /* write message to the browser console */
293 NGY2Tools.NanoConsoleLog = function(context, msg) {
294 if (window.console) { console.log('nanogallery2: ' + msg + ' [' + context.baseEltID + ']'); }
295 // debugger;
296 };
297
298
299 /** @function PreloaderDisplay() */
300 /* Display/hide preloader */
301 NGY2Tools.PreloaderDisplay = function(display) {
302 if( display === true ) {
303 // loading bar at the top of the gallery
304 this.$E.conLoadingB.removeClass('nanoGalleryLBarOff').addClass('nanoGalleryLBar');
305 // spinner over album thumbnail
306 if( this.GOM.albumIdxLoading != undefined && this.GOM.albumIdxLoading != -1 ) {
307 let item = this.I[this.GOM.albumIdxLoading];
308 item.$Elts['.nGY2TnImg'].addClass('nGY2GThumbnailLoaderDisplayed');
309 }
310 }
311 else {
312 // loading bar at the top of the gallery
313 this.$E.conLoadingB.removeClass('nanoGalleryLBar').addClass('nanoGalleryLBarOff');
314 // spinner over album thumbnail
315 if( this.GOM.albumIdxLoading != undefined && this.GOM.albumIdxLoading != -1 ) {
316 let item = this.I[this.GOM.albumIdxLoading];
317 item.$Elts['.nGY2TnImg'].removeClass('nGY2GThumbnailLoaderDisplayed');
318 }
319 }
320 };
321
322 // Scrambles the elements of an array
323 //+ Jonas Raoni Soares Silva
324 //@ http://jsfromhell.com/array/shuffle [v1.0]
325 NGY2Tools.AreaShuffle = function (o) {
326 for (var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
327 return o;
328 };
329
330 /** @function GetImageTitleFromURL() */
331 /* retrieve filemane */
332 NGY2Tools.GetImageTitleFromURL = function( imageURL ) {
333 if( this.O.thumbnailLabel.get('title') == '%filename' ) {
334 return (imageURL.split('/').pop()).replace('_',' ');
335 }
336
337 if( this.O.thumbnailLabel.get('title') == '%filenameNoExt' ) {
338 var s=imageURL.split('/').pop();
339 return (s.split('.').shift()).replace('_',' ');
340 }
341 // return imageURL;
342 return '';
343 };
344
345
346 /** @function AlbumPostProcess() */
347 /* post process one album based on plugin general parameters --> sorting/maxItems*/
348 NGY2Tools.AlbumPostProcess = function(albumID) {
349
350 // this function can probably be optimized....
351
352 var sortOrder = this.gallerySorting[this.GOM.curNavLevel];
353 var maxItems = this.galleryMaxItems[this.GOM.curNavLevel];
354
355 if( sortOrder != '' || maxItems > 0 ) {
356
357 // copy album's items to a new array
358 var currentAlbum = this.I.filter( function( obj ) {
359 return( obj.albumID == albumID && obj.kind != 'albumUp' );
360 });
361
362 // sorting options
363 switch( sortOrder ) {
364 case 'RANDOM':
365 currentAlbum = NGY2Tools.AreaShuffle(currentAlbum);
366 break;
367 case 'REVERSED':
368 currentAlbum = currentAlbum.reverse();
369 break;
370 case 'TITLEASC':
371 currentAlbum.sort(function (a, b) {
372 return( (a.title.toUpperCase() < b.title.toUpperCase()) ? -1 : ((a.title.toUpperCase() > b.title.toUpperCase()) ? 1 : 0) );
373 });
374 break;
375 case 'TITLEDESC':
376 currentAlbum.sort(function (a, b) {
377 return( (a.title.toUpperCase() > b.title.toUpperCase()) ? -1 : ((a.title.toUpperCase() < b.title.toUpperCase()) ? 1 : 0) );
378 });
379 break;
380 }
381
382 // max Items
383 if( maxItems > 0 && currentAlbum.length > maxItems ) {
384 currentAlbum.splice(maxItems - 1, currentAlbum.length-maxItems );
385 }
386
387 // remove the albums's items from the global items array
388 this.I.ngy2removeIf( function( obj ) {
389 return( obj.albumID == albumID && obj.kind != 'albumUp' );
390 });
391
392 // add the sorted items back to the album
393 this.I.push.apply(this.I, currentAlbum);
394
395 }
396 };
397
398 return NGY2Tools;
399 })();
400
401 // ====================
402 // ===== NGY2Item =====
403 // ====================
404 window.NGY2Item = (function() {
405 var nextId = 1; // private static --> all instances
406
407 // constructor
408 function NGY2Item( itemID ) {
409 //window.NGY2Item = function( itemID ) {
410 var ID = 0; // private
411
412 // public (this instance only)
413 if( itemID === undefined || itemID === null ) {
414 ID = nextId++;
415 }
416 else {
417 ID = itemID;
418 }
419 this.GetID = function () { return ID; };
420
421 // public
422 this.kind = ''; // 'image', 'album' or 'albumUp'
423 this.mediaKind = 'img'; // 'img', 'iframe', 'video'
424 this.mediaMarkup = '';
425 this.G = null; // pointer to global instance
426 this.title = ''; // image title
427 this.description = ''; // image description
428 this.albumID = 0; // ID of the parent album
429 this.src = ''; // full sized image URL
430 this.width = 0; // image width
431 this.height = 0; // image height
432 this.destinationURL = ''; // thumbnail destination URL --> open URL instead of displaying image
433 this.downloadURL = ''; // thumbnail download URL --> specify the image for download button
434 this.author = ''; // image/album author
435 this.left = 0; // store position to animate from old to new
436 this.top = 0;
437 this.width = 0; // store size to avoid setting width/height if not required
438 this.height = 0;
439 this.resizedContentWidth= 0; // store size of content (image) to avoid setting width/height if not required
440 this.resizedContentHeight= 0;
441 this.thumbs = { // URLs and sizes for user defined
442 url: { l1: { xs: '', sm:'', me: '', la: '', xl: '' }, lN: { xs: '', sm: '', me: '', la:'', xl: '' } },
443 width: { l1: { xs: 0, sm: 0, me: 0, la: 0 , xl: 0 }, lN: { xs: 0 , sm: 0, me: 0, la: 0, xl: 0 } },
444 height: { l1: { xs: 0, sm: 0, me: 0, la: 0 , xl: 0 }, lN: { xs: 0, sm: 0, me: 0, la: 0, xl: 0 } }
445 };
446 this.thumbnailImgRevealed = false; // thumbnail image already revealed
447 this.imageDominantColors = null; // base64 GIF
448 this.imageDominantColor = null; // HEX RGB
449 this.featured = false; // featured element
450 this.flickrThumbSizes = {}; // store URLs for all available thumbnail sizes (flickr)
451 this.picasaThumbs = null; // store URLs and sizes
452 this.hovered = false; // is the thumbnail currently hovered?
453 this.hoverInitDone = false;
454 this.contentIsLoaded = false; // album: are items already loaded?
455 this.contentLength = 0; // album: number of items (real number of items in memory)
456 this.numberItems = 0; // album: number of items (value returned by data source)
457 this.mediaNumber = 0; // media number in the album
458 this.mediaCounter = 0; // number of medias in an album
459 this.eltTransform = []; // store the CSS transformations
460 this.eltFilter = []; // store the CSS filters
461 this.eltEffect = []; // store data about hover effects animations
462 this.paginationLastPage = 0; // for albums
463 this.paginationLastWidth = 0; // for albums
464 this.customData = {};
465 this.selected = false;
466 this.imageWidth = 0; // image natural (real) width
467 this.imageHeight = 0; // image natural (real) height
468 this.$elt = null; // pointer to the corresponding DOM element
469 this.$Elts = []; // cached pointers to the thumbnail content -> to avoid jQuery().find()
470 this.tags = []; // list of tags of the current item
471 this.albumTagList = []; // list of all the tags of the items contained in the current album
472 this.albumTagListSel = []; // list of currently selected tags (only for albums)
473 this.exif = { exposure: '', flash: '', focallength: '', fstop: '', iso: '', model: '', time: '', location: ''};
474 this.deleted = false; // item is deleted -> do not display anymore
475 this.rotationAngle = 0; // image display rotation angle
476 }
477
478 // public static
479
480 NGY2Item.Get = function( instance, ID ) {
481 var l = instance.I.length;
482 for( var i = 0; i < l; i++ ) {
483 if( instance.I[i].GetID() == ID ) {
484 return instance.I[i];
485 }
486 }
487 return null;
488 };
489
490 NGY2Item.GetIdx = function( instance, ID ) {
491 var l = instance.I.length;
492 for( var i = 0; i < l; i++ ) {
493 if( instance.I[i].GetID() == ID ) {
494 return i;
495 }
496 }
497 return -1;
498 };
499
500 // create new item (image, album or albumUp)
501 NGY2Item.New = function( instance, title, description, ID, albumID, kind, tags ) {
502 var album = NGY2Item.Get( instance, albumID );
503
504 // title translation
505 if( instance.O.titleTranslationMap !== null ) {
506 let obj = instance.O.titleTranslationMap.find(o => o.title === title);
507 if( obj !== undefined ) {
508 title = obj.replace;
509 }
510 }
511
512
513 if( albumID != -1 && albumID != 0 && title !='image gallery by nanogallery2 [build]' ) {
514 if( instance.O.thumbnailLevelUp && album.getContentLength(false) == 0 && instance.O.album == '' ) {
515 // add navigation thumbnail (album up)
516 let item = new NGY2Item('0');
517 instance.I.push( item );
518 album.contentLength += 1;
519 item.title = 'UP';
520 item.albumID = albumID;
521 item.kind = 'albumUp';
522 item.G = instance;
523
524 jQuery.extend( true, item.thumbs.width, instance.tn.defaultSize.width);
525 jQuery.extend( true, item.thumbs.height, instance.tn.defaultSize.height);
526 }
527 }
528
529 var item = NGY2Item.Get(instance, ID);
530 if( item === null ){
531 // create a new item (otherwise, just update the existing one)
532 item = new NGY2Item(ID);
533 instance.I.push(item);
534 if( albumID != -1 && title !='image gallery by nanogallery2 [build]' ) {
535 album.contentLength+=1;
536 }
537 }
538 item.G = instance;
539
540 item.albumID = albumID;
541 item.kind = kind;
542 if( kind == 'image' ) {
543 album.mediaCounter += 1;
544 item.mediaNumber = album.mediaCounter;
545 }
546
547 // check keyword to find features images/albums
548 var kw = instance.O.thumbnailFeaturedKeyword;
549 if( kw != '' ) {
550 // check if item featured based on a keyword in the title or in the description
551 kw = kw.toUpperCase();
552 var p = title.toUpperCase().indexOf(kw);
553 if( p > -1) {
554 item.featured = true;
555 // remove keyword case unsensitive
556 title = title.substring(0, p) + title.substring(p+kw.length, title.length);
557 }
558 p = description.toUpperCase().indexOf(kw);
559 if( p > -1) {
560 item.featured=true;
561 // remove keyword case unsensitive
562 description=description.substring(0, p) + description.substring(p + kw.length, description.length);
563 }
564 }
565
566 // TAGS
567 // if( instance.galleryFilterTags.Get() != false ) {
568 // if( instance.galleryFilterTags.Get() == true ) {
569 // if( tags != '' && tags != undefined ) {
570 // use set tags
571 // item.setTags(tags.split(' '));
572 // }
573 // }
574 // else {
575 // extract tags starting with # (in title)
576 if( typeof instance.galleryFilterTags.Get() == 'string' ) {
577 switch( instance.galleryFilterTags.Get().toUpperCase() ) {
578 case 'TITLE': {
579 let re = /(?:^|\W)#(\w+)(?!\w)/g, match, matches = [];
580 // let tags = "";
581 while (match = re.exec(title)) {
582 matches.push(match[1].replace(/^\s*|\s*$/, '')); //trim trailing/leading whitespace
583 }
584 item.setTags(matches); //tags;
585 title = title.split('#').join(''); //replaceall
586 break;
587 }
588 case 'DESCRIPTION': {
589 let re = /(?:^|\W)#(\w+)(?!\w)/g, match2, matches2 = [];
590 // let tags = "";
591 while (match2 = re.exec(description)) {
592 matches2.push(match2[1].replace(/^\s*|\s*$/, '')); //trim trailing/leading whitespace
593 }
594 item.setTags(matches2); //tags;
595 description = description.split('#').join(''); //replaceall
596 break;
597 }
598 }
599 }
600 else {
601 if( tags != '' && tags != undefined ) {
602 // use set tags
603 item.setTags(tags.split(' '));
604 }
605 }
606 // }
607 // }
608
609 // set (maybe modified) fields title and description
610 item.title = escapeHtml(instance, title);
611 item.description = escapeHtml(instance, description);
612 return item;
613 };
614
615
616 // removes logically current item
617 NGY2Item.prototype.delete = function( ) {
618 this.deleted = true;
619
620 // update content length of parent album
621 this.G.I[NGY2Item.GetIdx(this.G, this.albumID)].contentLength--;
622 this.G.I[NGY2Item.GetIdx(this.G, this.albumID)].numberItems--;
623
624 // check if in GOM and removes it
625 var nbTn = this.G.GOM.items.length;
626 var ID = this.GetID();
627 var foundIdx = -1;
628 var foundGOMidx = -1;
629 for( var i = 0; i < nbTn ; i++ ) {
630 var curTn = this.G.GOM.items[i];
631 var item=this.G.I[curTn.thumbnailIdx];
632 if( item.GetID() == ID ) {
633 // FOUND
634 if( !curTn.neverDisplayed ) {
635 foundIdx = curTn.thumbnailIdx;
636 foundGOMidx = i;
637 }
638 }
639 else {
640 if( foundIdx != -1 ) {
641 if( !curTn.neverDisplayed ) {
642 // update index value
643 item.$getElt('.nGY2GThumbnail').data('index', i-1);
644 item.$getElt('.nGY2GThumbnailImg').data('index', i-1);
645 }
646 }
647 }
648 }
649 if( foundIdx != -1 ) {
650 // delete item in GOM and delete thumbnail
651 var G = this.G;
652 if( this.selected == true ) {
653 this.selected = false;
654 G.GOM.nbSelected--; // update the global counter
655 }
656 if( G.I[foundIdx].$elt !== null ) {
657 G.I[foundIdx].$elt.remove(); // delete thumbnail DOM object
658 }
659 G.GOM.items.splice(foundGOMidx, 1); // delete in GOM
660 if( G.GOM.lastDisplayedIdx != -1 ) {
661 G.GOM.lastDisplayedIdx -= 1;
662 }
663 }
664
665 // TODO: update media-number of the other item in the same album
666 }
667
668 NGY2Item.prototype.addToGOM = function( ) {
669 // retrieve index
670 var ID = this.GetID();
671 var l = this.G.I.length;
672 for( var idx = 0; idx < l; idx++ ) {
673 var item = this.G.I[idx];
674 if( item.GetID() == ID ) {
675 var w = item.thumbImg().width;
676 var h = item.thumbImg().height;
677 // set default size if required
678 if( h == 0 ) {
679 h = this.G.tn.defaultSize.getHeight();
680 }
681 if( w == 0 ) {
682 w = this.G.tn.defaultSize.getWidth();
683 }
684 // add to GOM -> will be displayed on next refresh/resize
685 var tn = new this.G.GOM.GTn(idx, w, h);
686 this.G.GOM.items.push(tn);
687 break;
688 }
689 }
690
691 }
692
693
694 // function to avoid XSS issue - Cross Site Scripting
695 // original: https://github.com/janl/mustache.js/blob/master/mustache.js#L55
696 var entityMap = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;', '/': '&#x2F;', '`': '&#x60;', '=': '&#x3D;' };
697 function escapeHtml (instance, string) {
698 if( instance.O.allowHTMLinData == true ) {
699 return string;
700 }
701 else {
702 return String(string).replace(/[&<>"'`=\/]/g, function fromEntityMap (s) {
703 return entityMap[s];
704 });
705 }
706 }
707
708
709 NGY2Item.get_nextId = function () {
710 return nextId;
711 };
712
713 //=== public (shared across instances)
714
715 //--- cached sub elements
716 NGY2Item.prototype.$getElt = function( elt, forceRefresh ) {
717 if( this.$elt == null ) { return null; }
718 if( this.$Elts[elt] !== undefined && !forceRefresh == true ) {
719 return this.$Elts[elt];
720 }
721 else {
722 if( elt == '.nGY2GThumbnail' ) {
723 this.$Elts[elt]=this.$elt;
724 }
725 else {
726 this.$Elts[elt]=this.$elt.find(elt);
727 }
728 return this.$Elts[elt];
729 }
730 };
731
732 // remove one element (in DOM and in cache)
733 NGY2Item.prototype.removeElt = function( elt ) {
734 if( this.$elt == null ) { return; }
735 if( this.$Elts[elt] == undefined) { return; }
736 this.$Elts[elt].remove();
737 var index = this.$Elts.indexOf(elt);
738 this.$Elts.splice(index, 1);
739 };
740
741 //--- returns the album containing the item
742 NGY2Item.prototype.album = function() {
743 return this.G.I[NGY2Item.GetIdx(this.G, this.albumID)];
744 };
745
746 //--- viewer - transition can be disabled per media kind - returns true if current media supports transition (swipe)
747 NGY2Item.prototype.mediaTransition = function( ) {
748 if( this.G.O.viewerTransitionMediaKind.indexOf( this.mediaKind ) > -1 ) {
749 return true;
750 }
751 return false;
752 };
753
754 //--- set one image (url and size)
755 NGY2Item.prototype.imageSet = function( src, w, h ) {
756 this.src = src;
757 this.width = w;
758 this.height = h;
759 };
760
761 //--- set one thumbnail (url and size) - screenSize and level are optional
762 NGY2Item.prototype.thumbSet = function( src, w, h, screenSize, level ) {
763 var lst=['xs','sm','me','la','xl'];
764 if( typeof screenSize === 'undefined' || screenSize == '' || screenSize == null ) {
765 for( var i=0; i< lst.length; i++ ) {
766 if( typeof level === 'undefined' || level == '' ) {
767 this.thumbs.url.l1[lst[i]]=src;
768 this.thumbs.height.l1[lst[i]]=h;
769 this.thumbs.width.l1[lst[i]]=w;
770 this.thumbs.url.lN[lst[i]]=src;
771 this.thumbs.height.lN[lst[i]]=h;
772 this.thumbs.width.lN[lst[i]]=w;
773 }
774 else {
775 this.thumbs.url[level][lst[i]]=src;
776 this.thumbs.height[level][lst[i]]=h;
777 this.thumbs.width[level][lst[i]]=w;
778 }
779 }
780 }
781 else {
782 if( typeof level === 'undefined' || level == '' || level == null ) {
783 this.thumbs.url.l1[screenSize]=src;
784 this.thumbs.height.l1[screenSize]=h;
785 this.thumbs.width.l1[screenSize]=w;
786 this.thumbs.url.lN[screenSize]=src;
787 this.thumbs.height.lN[screenSize]=h;
788 this.thumbs.width.lN[screenSize]=w;
789 }
790 else {
791 this.thumbs.url[level][screenSize]=src;
792 this.thumbs.height[level][screenSize]=h;
793 this.thumbs.width[level][screenSize]=w;
794 }
795 }
796
797 for( var i=0; i< lst.length; i++ ) {
798 this.thumbs.height.l1[lst[i]]=h;
799 }
800 for( var i=0; i< lst.length; i++ ) {
801 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() ) {
802 this.thumbs.height.lN[lst[i]]=h;
803 }
804 }
805 };
806
807 //--- set thumbnail image real height for current level/resolution, and for all others level/resolutions having the same settings
808 NGY2Item.prototype.thumbSetImgHeight = function( h ) {
809 var lst=['xs','sm','me','la','xl'];
810 for( var i=0; i< lst.length; i++ ) {
811 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() ) {
812 this.thumbs.height.l1[lst[i]]=h;
813 }
814 }
815 for( var i=0; i< lst.length; i++ ) {
816 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() ) {
817 this.thumbs.height.lN[lst[i]]=h;
818 }
819 }
820 };
821
822 //--- set thumbnail image real width for current level/resolution, and for all others level/resolutions having the same settings
823 NGY2Item.prototype.thumbSetImgWidth = function( w ) {
824 var lst=['xs','sm','me','la','xl'];
825 for( var i=0; i< lst.length; i++ ) {
826 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() ) {
827 this.thumbs.width.l1[lst[i]]=w;
828 }
829 }
830 for( var i=0; i< lst.length; i++ ) {
831 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() ) {
832 this.thumbs.width.lN[lst[i]]=w;
833 }
834 }
835 };
836
837 //--- Returns Thumbnail image (depending of the screen resolution)
838 NGY2Item.prototype.thumbImg = function () {
839 var tnImg = { src: '', width: 0, height: 0 };
840
841 if( this.title == 'image gallery by nanogallery2 [build]' ) {
842 tnImg.src = this.G.emptyGif;
843 tnImg.url = this.G.emptyGif;
844 return tnImg;
845 }
846 tnImg.src = this.thumbs.url[this.G.GOM.curNavLevel][this.G.GOM.curWidth];
847 tnImg.width = this.thumbs.width[this.G.GOM.curNavLevel][this.G.GOM.curWidth];
848 tnImg.height = this.thumbs.height[this.G.GOM.curNavLevel][this.G.GOM.curWidth];
849 return tnImg;
850 };
851
852 //--- Set tags to items and add these tags to the album
853 NGY2Item.prototype.setTags = function( tags ) {
854 if( tags.length > 0 ) {
855 this.tags = tags;
856 var lstTags = this.album().albumTagList;
857 for( var i = 0; i < tags.length; i++ ) {
858 var tfound = false;
859 for( var j = 0; j < lstTags.length; j++ ) {
860 if( tags[i].toUpperCase() == lstTags[j].toUpperCase() ) {
861 tfound = true;
862 }
863 }
864 if( tfound == false) {
865 this.album().albumTagList.push(tags[i])
866 // this.album().albumTagListSel.push(tags[i])
867 }
868 }
869 }
870 };
871
872 //--- check if 1 of current item's tags is selected (tag filter)
873 NGY2Item.prototype.checkTagFilter = function() {
874 if( this.G.galleryFilterTags.Get() != false && this.album().albumTagList.length > 0 ) {
875 if( this.G.O.thumbnailLevelUp && this.kind == 'albumUp' ) {
876 return true;
877 }
878 var found = false;
879 var lstTags = this.album().albumTagListSel;
880 if( lstTags.length == 0 ) {
881 // no tag is selected -> display all items
882 return true;
883 }
884 for( var i = 0; i < this.tags.length; i++ ) {
885 for( var j = 0; j < lstTags.length; j++ ) {
886 if( this.tags[i].toUpperCase() == lstTags[j].toUpperCase() ) {
887 found = true;
888 break;
889 }
890 }
891 }
892 return found;
893 }
894 else
895 return true;
896 };
897
898 //--- check if 1 of current item's tags is found using API search
899 NGY2Item.prototype.isSearchTagFound = function() {
900 if( this.G.GOM.albumSearchTags == '' ) { return true; }
901 if( this.G.O.thumbnailLevelUp && this.kind == 'albumUp' ) { return true; }
902
903 //var lstTags=this.album().albumTagListSel;
904 for( var i = 0; i < this.tags.length; i++ ) {
905 if( this.tags[i].toUpperCase().indexOf( this.G.GOM.albumSearchTags ) >= 0 ) {
906 return true;
907 }
908 }
909 return false;
910 };
911
912 //--- set the URL of the media to display in the viewer
913 //--- markup is defined for images
914 NGY2Item.prototype.setMediaURL = function( url, mediaKind ) {
915 this.src = url;
916 this.mediaKind = mediaKind;
917 if( mediaKind == 'img' ) {
918 this.mediaMarkup = '<img class="nGY2ViewerMedia" src="' + url + '" alt=" " itemprop="contentURL" draggable="false">';
919 }
920 };
921
922
923 //--- check if current item should be displayed
924 NGY2Item.prototype.isToDisplay = function( albumID ) {
925 return this.albumID == albumID && this.checkTagFilter() && this.isSearchFound() && this.isSearchTagFound() && this.deleted == false;
926 };
927
928
929
930 //--- returns the number of items of the current album
931 //--- count using tags filter
932 NGY2Item.prototype.getContentLength = function( filterTags ) {
933 if( filterTags == false || this.albumTagList.length == 0 || this.G.galleryFilterTags.Get() == false ) {
934 return this.contentLength;
935 }
936 else {
937 var l = this.G.I.length;
938 var cnt = 0;
939 var albumID = this.GetID();
940 for( var idx = 0; idx < l; idx++ ) {
941 var item = this.G.I[idx];
942 if( item.isToDisplay(albumID) ) {
943 cnt++;
944 }
945 }
946 return cnt;
947 }
948 };
949
950 NGY2Item.prototype.isSearchFound = function() {
951 if( this.G.GOM.albumSearch != '' ) {
952 if( this.title.toUpperCase().indexOf( this.G.GOM.albumSearch ) == -1 ) {
953 return false;
954 }
955 }
956 return true;
957 }
958
959
960 //--- for future use...
961 NGY2Item.prototype.responsiveURL = function () {
962 var url = '';
963 switch(this.G.O.kind) {
964 case '':
965 url = this.src;
966 break;
967 case 'flickr':
968 url = this.src;
969 break;
970 case 'picasa':
971 case 'google':
972 case 'google2':
973 default:
974 url = this.src;
975 break;
976 }
977 return url;
978 };
979
980
981 //--- Reveal the thumbnail image with animation on opacity
982 NGY2Item.prototype.ThumbnailImageReveal = function () {
983
984 if( this.thumbnailImgRevealed == false ) {
985 this.thumbnailImgRevealed = true;
986 new NGTweenable().tween({
987 from: { opacity: 0 },
988 to: { opacity: 1 },
989 attachment: { item: this },
990 delay: 30,
991 duration: 400,
992 easing: 'easeOutQuart',
993 step: function (state, att) {
994 var $e=att.item.$getElt('.nGY2TnImg');
995 if( $e != null ) {
996 $e.css( state );
997 }
998 }
999 });
1000 }
1001 };
1002
1003
1004 // In case of thumbnails with stacks - apply a percent to a value which include a unit
1005 function ValueApplyPercent( str, percent ) {
1006 str=String(str);
1007 if( str === '0' || percent == 1 ) { return str; }
1008 var n = Number(str.replace(/[a-zA-Z]/g, ''));
1009 var ar = str.match(/([^\-0-9\.]+)/g);
1010 var a = '';
1011 if( ar != null && ar.length > 0 ) {
1012 a = ar.join();
1013 }
1014
1015 if( isNaN(n) || n == 0 ) {
1016 return str;
1017 }
1018
1019 n = n * percent;
1020 return n + a;
1021 }
1022
1023 //--- 2D/3D CSS transform - apply the cached value to element
1024 NGY2Item.prototype.CSSTransformApply = function ( eltClass ) {
1025 var obj = this.eltTransform[eltClass];
1026
1027 if( eltClass == '.nGY2GThumbnail' ) {
1028 // thumbnail
1029 var nbStacks = obj.$elt.length-1;
1030 var pTranslateX = 1;
1031 var pTranslateY = 1;
1032 var pTranslateZ = 1;
1033 var pTranslate = 1;
1034 var pRotateX = 1;
1035 var pRotateY = 1;
1036 var pRotateZ = 1;
1037 var pRotate = 1;
1038 var pScale = 1;
1039 for( var n = nbStacks; n >= 0; n-- ) {
1040 // units must be given with
1041 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) + ')';
1042 if( !(this.G.IE <= 9) && !this.G.isGingerbread ) {
1043 v += ' rotateX(' + ValueApplyPercent(obj.rotateX,pRotateX) + ') rotateY(' + ValueApplyPercent(obj.rotateY,pRotateY) + ') rotateZ(' + ValueApplyPercent(obj.rotateZ,pRotateZ) + ') rotate(' + ValueApplyPercent(obj.rotate,pRotate) + ')';
1044 }
1045 else {
1046 v += ' rotate(' + ValueApplyPercent(obj.rotateZ,pRotateZ) + ')';
1047 }
1048 obj.$elt[n].style[this.G.CSStransformName] = v;
1049
1050 if( nbStacks > 0 ) {
1051 // apply a percent to the stack elements
1052 pTranslateX -= this.G.tn.opt.Get('stacksTranslateX');
1053 pTranslateY -= this.G.tn.opt.Get('stacksTranslateY');
1054 pTranslateZ -= this.G.tn.opt.Get('stacksTranslateZ');
1055 pRotateX -= this.G.tn.opt.Get('stacksRotateX');
1056 pRotateY -= this.G.tn.opt.Get('stacksRotateY');
1057 pRotateZ -= this.G.tn.opt.Get('stacksRotateZ');
1058 pScale -= this.G.tn.opt.Get('stacksScale');
1059 }
1060 }
1061 }
1062 else {
1063 // thumbnail sub element
1064 if( obj.$elt != null ) {
1065 for( var n = 0; n < obj.$elt.length; n++ ) {
1066 if( obj.$elt[n] != undefined ) {
1067 // units must be given with
1068 var v = 'translateX(' + obj.translateX + ') translateY(' + obj.translateY + ') translateZ(' + obj.translateZ + ') scale(' + obj.scale + ') translate(' + obj.translate + ')';
1069 if( !(this.G.IE <= 9) && !this.G.isGingerbread ) {
1070 v += ' rotateX(' + obj.rotateX + ') rotateY(' + obj.rotateY + ') rotateZ(' + obj.rotateZ + ') rotate(' + obj.rotate + ')';
1071 }
1072 else {
1073 v += ' rotate(' + obj.rotateZ + ')';
1074 }
1075 obj.$elt[n].style[this.G.CSStransformName] = v;
1076 }
1077 }
1078 }
1079 }
1080 };
1081
1082 //--- 2D/3D CSS transform - set a value in cache
1083 NGY2Item.prototype.CSSTransformSet = function ( eltClass, transform, value, forceRefresh ) {
1084 if( this.eltTransform[eltClass] == undefined ) {
1085 this.eltTransform[eltClass] = { translateX: 0, translateY: 0, translateZ: 0, rotateX: 0, rotateY: 0, rotateZ: 0, scale: 1, translate: '0px,0px', rotate: 0 };
1086 this.eltTransform[eltClass].$elt = this.$getElt(eltClass);
1087 }
1088 this.eltTransform[eltClass][transform] = value;
1089 if( forceRefresh === true ) {
1090 this.eltTransform[eltClass].$elt = this.$getElt(eltClass, true);
1091 }
1092 };
1093
1094 //--- CSS Filters - apply the cached value to element
1095 NGY2Item.prototype.CSSFilterApply = function ( eltClass ) {
1096 var obj = this.eltFilter[eltClass];
1097 var v = 'blur(' + obj.blur + ') brightness(' + obj.brightness + ') grayscale(' + obj.grayscale + ') sepia(' + obj.sepia + ') contrast(' + obj.contrast + ') opacity(' + obj.opacity + ') saturate(' + obj.saturate + ')';
1098 if( obj.$elt != null ) {
1099 for( var n = 0; n < obj.$elt.length; n++ ) {
1100 if( obj.$elt[n] != undefined ) {
1101 obj.$elt[n].style.WebkitFilter = v;
1102 obj.$elt[n].style.filter = v;
1103 }
1104 }
1105 }
1106 };
1107
1108 //--- CSS Filters - set a value in cache
1109 NGY2Item.prototype.CSSFilterSet = function ( eltClass, filter, value, forceRefresh ) {
1110 if( this.eltFilter[eltClass] == undefined ) {
1111 this.eltFilter[eltClass] = { blur: 0, brightness: '100%', grayscale: '0%', sepia: '0%', contrast: '100%', opacity: '100%', saturate: '100%' };
1112 this.eltFilter[eltClass].$elt = this.$getElt(eltClass);
1113 }
1114 this.eltFilter[eltClass][filter] = value;
1115 if( forceRefresh === true ) {
1116 this.eltTransform[eltClass].$elt = this.$getElt(eltClass, true);
1117 }
1118 };
1119
1120 //--- thumbnail hover animation
1121 NGY2Item.prototype.animate = function ( effect, delay, hoverIn ) {
1122 if( this.$getElt() == null ) { return; }
1123
1124 var context = {};
1125 context.G = this.G;
1126 context.item = this;
1127 context.effect = effect;
1128 context.hoverIn = hoverIn;
1129 context.cssKind = '';
1130 if( hoverIn ) {
1131 // HOVER IN
1132
1133 if( this.eltEffect[effect.element] == undefined ) {
1134 this.eltEffect[effect.element] = [];
1135 }
1136 if( this.eltEffect[effect.element][effect.type] == undefined ) {
1137 this.eltEffect[effect.element][effect.type] = { initialValue: 0, lastValue: 0 };
1138 }
1139 if( effect.firstKeyframe ) {
1140 // store initial and current value -> for use in the back animation
1141 this.eltEffect[effect.element][effect.type] = { initialValue: effect.from, lastValue: effect.from};
1142 }
1143
1144 context.animeFrom = effect.from;
1145 context.animeTo = effect.to;
1146 context.animeDuration = parseInt(effect.duration);
1147 context.animeDelay = 30 + parseInt(effect.delay + delay); // 30ms is a default delay to avoid conflict with other initializations
1148 context.animeEasing = effect.easing;
1149 }
1150 else {
1151 // HOVER OUT
1152 // if( effect.firstKeyframe ) {
1153 context.animeFrom = this.eltEffect[effect.element][effect.type].lastValue;
1154 context.animeTo = this.eltEffect[effect.element][effect.type].initialValue;
1155 // context.animeTo=effect.from;
1156 // }
1157 // else {
1158 // // context.animeFrom=effect.from;
1159 // context.animeFrom = this.eltEffect[effect.element][effect.type].lastValue;
1160 // context.animeTo = this.eltEffect[effect.element][effect.type].initialValue;
1161 // //context.animeTo=effect.to;
1162
1163 // }
1164
1165 context.animeDuration = parseInt(effect.durationBack);
1166 context.animeDelay = 30 + parseInt(effect.delayBack + delay); // 30ms is a default delay to avoid conflict with other initializations
1167 context.animeEasing = effect.easingBack;
1168 }
1169
1170
1171 // detect if animation on CSS transform
1172 var transform=['translateX', 'translateY', 'translateZ', 'scale', 'rotateX', 'rotateY', 'rotateZ'];
1173 for( var i = 0; i < transform.length; i++ ) {
1174 if( effect.type == transform[i] ) {
1175 context.cssKind = 'transform';
1176 break;
1177 }
1178 }
1179
1180 // detect if animation on CSS filter
1181 var filter=['blur', 'brightness', 'grayscale', 'sepia', 'contrast', 'opacity', 'saturate'];
1182 for( var i = 0; i < filter.length; i++ ) {
1183 if( effect.type == filter[i] ) {
1184 context.cssKind = 'filter';
1185 break;
1186 }
1187 }
1188 // handle some special cases
1189 if( hoverIn && effect.element == '.nGY2GThumbnail' && ( effect.type == 'scale' || effect.type == 'rotateX') ) {
1190 this.G.GOM.lastZIndex++;
1191 this.$getElt(effect.element).css('z-index', this.G.GOM.lastZIndex);
1192 // setElementOnTop(this.G.$E.base, this.$getElt(effect.element) );
1193 }
1194
1195 // animation
1196 var tweenable = new NGTweenable();
1197 context.tweenable=tweenable;
1198 tweenable.tween({
1199 attachment: context,
1200 from: { 'v': context.animeFrom },
1201 to: { 'v': context.animeTo },
1202 duration: context.animeDuration, //parseInt(effect.duration),
1203 delay: context.animeDelay, //parseInt(effect.delay),
1204 easing: context.animeEasing, //'easeOutQuart',
1205
1206 step: function (state, att) {
1207 if( att.item.$getElt() == null ) {
1208 // the thumbnail may be destroyed since the start of the animation
1209 att.tweenable.stop(false);
1210 // att.tweenable.dispose();
1211 return;
1212 }
1213 if( att.hoverIn && !att.item.hovered ) {
1214 // thumbnail no more hovered
1215 att.tweenable.stop(false);
1216 // att.tweenable.dispose();
1217 return;
1218 }
1219
1220 if( att.G.VOM.viewerDisplayed ) {
1221 att.tweenable.stop(false);
1222 // att.tweenable.dispose();
1223 return;
1224 }
1225
1226 // test if in delay phase
1227 if( state.v == att.animeFrom ) { return; }
1228
1229 switch( att.cssKind ) {
1230 case 'transform':
1231 // window.ng_draf( function() {
1232 att.item.CSSTransformSet(att.effect.element, att.effect.type, state.v);
1233 att.item.CSSTransformApply( att.effect.element );
1234 // });
1235 break;
1236 case 'filter':
1237 // window.ng_draf( function() {
1238 att.item.CSSFilterSet(att.effect.element, att.effect.type, state.v);
1239 att.item.CSSFilterApply( att.effect.element );
1240 // });
1241 break;
1242 default:
1243 var v=state.v;
1244 if( state.v.substring(0,4) == 'rgb(' || state.v.substring(0,5) == 'rgba(' ) {
1245 // to remove values after the dot (not supported by RGB/RGBA)
1246 // v=ngtinycolor(state.v).toRgbString();
1247 v = ShadeBlendConvert(0, v);
1248 }
1249 // window.ng_draf( function() {
1250 att.item.$getElt( att.effect.element ).css( att.effect.type, v );
1251 // });
1252 break;
1253 }
1254 if( hoverIn ) {
1255 // store value for back animation
1256 att.item.eltEffect[att.effect.element][att.effect.type].lastValue = state.v;
1257 }
1258 },
1259
1260 finish: function (state, att) {
1261 if( hoverIn ) {
1262 // store value for back animation
1263 att.item.eltEffect[att.effect.element][att.effect.type].lastValue = state.v;
1264 }
1265
1266 if( att.item.$getElt() == null ) {
1267 // the thumbnail may be destroyed since the start of the animation
1268 return;
1269 }
1270 if( att.hoverIn && !att.item.hovered ) {
1271 // thumbnail no more hovered
1272 return;
1273 }
1274
1275 if( att.G.VOM.viewerDisplayed ) {
1276 return;
1277 }
1278
1279 // window.ng_draf( function() {
1280 switch( att.cssKind ) {
1281 case 'transform':
1282 att.item.CSSTransformSet(att.effect.element, att.effect.type, att.animeTo);
1283 att.item.CSSTransformApply(att.effect.element);
1284 break;
1285 case 'filter':
1286 att.item.CSSFilterSet(att.effect.element, att.effect.type, att.animeTo);
1287 att.item.CSSFilterApply(att.effect.element);
1288 break;
1289 default:
1290 att.item.$getElt(att.effect.element).css(att.effect.type, att.animeTo);
1291 break;
1292 }
1293 // });
1294 }
1295 });
1296 };
1297
1298 return NGY2Item;
1299 })();
1300
1301 }
1302
1303 _this.options = jQuery.extend(true, {}, jQuery.nanogallery2.defaultOptions, options);
1304 // Initialization code
1305 _this.nG2 = null;
1306 _this.nG2 = new nanoGALLERY2();
1307 _this.nG2.initiateGallery2(_this.e, _this.options );
1308
1309 };
1310
1311 // PUBLIC EXPOSED METHODS
1312 _this.test = function() {
1313 //alert('test');
1314 // console.dir(_this.nG.G.I.length);
1315 // console.dir(_this.nG);
1316 // debugger;
1317 //privateTest();
1318 }
1319
1320
1321 // Run initializer
1322 _this.init();
1323 };
1324
1325 jQuery.nanogallery2.defaultOptions = {
1326 kind : '',
1327 userID : '',
1328 photoset : '',
1329 album: '',
1330 blockList : 'scrapbook|profil|auto backup',
1331 tagBlockList: '',
1332 allowList : '',
1333 albumList : '',
1334 albumList2 : null,
1335 RTL : false,
1336 flickrSkipOriginal : true,
1337 flickrAPIKey: '',
1338 breadcrumbAutoHideTopLevel : true,
1339 displayBreadcrumb : true,
1340 breadcrumbOnlyCurrentLevel : true,
1341 breadcrumbHideIcons : true,
1342 theme : 'nGY2',
1343 galleryTheme : 'dark',
1344 viewerTheme : 'dark',
1345 items : null,
1346 itemsBaseURL : '',
1347 thumbnailSelectable : false,
1348 dataProvider: '',
1349 allowHTMLinData: false,
1350 locationHash : true,
1351 slideshowDelay : 3000,
1352 slideshowAutoStart : false,
1353
1354 debugMode: false,
1355
1356 titleTranslationMap: null,
1357 galleryDisplayMoreStep : 2,
1358 galleryDisplayMode : 'fullContent',
1359 galleryL1DisplayMode : null,
1360 galleryPaginationMode : 'rectangles', // 'dots', 'rectangles', 'numbers'
1361 galleryPaginationTopButtons : true,
1362 galleryMaxRows : 2,
1363 galleryL1MaxRows : null,
1364 galleryLastRowFull: false,
1365 galleryL1LastRowFull: null,
1366 galleryLayoutEngine : 'default',
1367 paginationSwipe: true,
1368 paginationVisiblePages : 10,
1369 galleryFilterTags : false, // possible values: false, true, 'title', 'description'
1370 galleryL1FilterTags : null, // possible values: false, true, 'title', 'description'
1371 galleryFilterTagsMode : 'single',
1372 galleryL1FilterTagsMode : null,
1373 galleryMaxItems : 0, // maximum number of items per album --> only flickr, google2, nano_photos_provider2
1374 galleryL1MaxItems : null, // maximum number of items per gallery page --> only flickr, google2, nano_photos_provider2
1375 gallerySorting : '',
1376 galleryL1Sorting : null,
1377 galleryDisplayTransition : 'none',
1378 galleryL1DisplayTransition : null,
1379 galleryDisplayTransitionDuration : 1000,
1380 galleryL1DisplayTransitionDuration : null,
1381 galleryResizeAnimation : false,
1382 galleryRenderDelay : 10,
1383
1384 thumbnailCrop : true,
1385 thumbnailL1Crop : null,
1386 thumbnailCropScaleFactor : 1.5,
1387 thumbnailLevelUp : false,
1388 thumbnailAlignment : 'fillWidth',
1389 thumbnailWidth : 300,
1390 thumbnailL1Width : null,
1391 thumbnailHeight : 200,
1392 thumbnailL1Height : null,
1393 thumbnailBaseGridHeight : 0,
1394 thumbnailL1BaseGridHeight : null,
1395 thumbnailGutterWidth : 2,
1396 thumbnailL1GutterWidth : null,
1397 thumbnailGutterHeight : 2,
1398 thumbnailL1GutterHeight : null,
1399 thumbnailBorderVertical : 2,
1400 thumbnailL1BorderVertical : null,
1401 thumbnailBorderHorizontal : 2,
1402 thumbnailL1BorderHorizontal : null,
1403 thumbnailFeaturedKeyword : '*featured',
1404 thumbnailAlbumDisplayImage : false,
1405 thumbnailHoverEffect2 : 'toolsAppear',
1406 thumbnailBuildInit2 : '',
1407 thumbnailStacks : 0,
1408 thumbnailL1Stacks : null,
1409 thumbnailStacksTranslateX : 0,
1410 thumbnailL1StacksTranslateX : null,
1411 thumbnailStacksTranslateY : 0,
1412 thumbnailL1StacksTranslateY : null,
1413 thumbnailStacksTranslateZ : 0,
1414 thumbnailL1StacksTranslateZ : null,
1415 thumbnailStacksRotateX : 0,
1416 thumbnailL1StacksRotateX : null,
1417 thumbnailStacksRotateY : 0,
1418 thumbnailL1StacksRotateY : null,
1419 thumbnailStacksRotateZ : 0,
1420 thumbnailL1StacksRotateZ : null,
1421 thumbnailStacksScale : 0,
1422 thumbnailL1StacksScale : null,
1423 thumbnailDisplayOutsideScreen: true,
1424 thumbnailWaitImageLoaded: true,
1425 thumbnailSliderDelay: 2000,
1426 galleryBuildInit2 : '',
1427 portable : false,
1428 eventsDebounceDelay: 10,
1429
1430 touchAnimation : false,
1431 touchAnimationL1 : undefined,
1432 touchAutoOpenDelay : 0,
1433
1434 thumbnailLabel : {
1435 position : 'overImage',
1436 align: 'center',
1437 valign: 'bottom',
1438 display : true,
1439 displayDescription : false,
1440 titleMaxLength : 0,
1441 titleMultiLine : false,
1442 descriptionMaxLength : 0,
1443 descriptionMultiLine : false,
1444 hideIcons : true,
1445 title : ''
1446 },
1447
1448 thumbnailToolbarImage : { topLeft: 'select', topRight : 'featured' },
1449 thumbnailToolbarAlbum : { topLeft: 'select', topRight : 'counter' },
1450 thumbnailDisplayOrder : '',
1451 thumbnailL1DisplayOrder : null,
1452 thumbnailDisplayInterval : 15,
1453 thumbnailL1DisplayInterval : null,
1454 thumbnailDisplayTransition : 'fadeIn',
1455 thumbnailL1DisplayTransition : null,
1456 thumbnailDisplayTransitionEasing : 'easeOutQuart',
1457 thumbnailL1DisplayTransitionEasing : null,
1458 thumbnailDisplayTransitionDuration: 240,
1459 thumbnailL1DisplayTransitionDuration: null,
1460 thumbnailOpenInLightox : true,
1461 thumbnailOpenOriginal : false,
1462
1463 lightboxStandalone: false,
1464 viewer : 'internal',
1465 viewerFullscreen: false,
1466 imageTransition : 'swipe2',
1467 viewerTransitionMediaKind : 'img',
1468 viewerZoom : true,
1469 viewerImageDisplay : '',
1470 openOnStart : '',
1471 viewerHideToolsDelay : 4000,
1472 viewerToolbar : {
1473 display : false,
1474 position : 'bottom',
1475 fullWidth : false,
1476 align : 'center',
1477 autoMinimize : 0,
1478 standard : 'minimizeButton,label',
1479 minimized : 'minimizeButton,label,infoButton,shareButton,fullscreenButton'
1480 },
1481 viewerTools : {
1482 topLeft : 'pageCounter,playPauseButton',
1483 topRight : 'rotateLeft,rotateRight,fullscreenButton,closeButton'
1484 },
1485 viewerGallery: 'bottomOverMedia',
1486 viewerGalleryTWidth: 40,
1487 viewerGalleryTHeight: 40,
1488
1489 breakpointSizeSM : 480,
1490 breakpointSizeME : 992,
1491 breakpointSizeLA : 1200,
1492 breakpointSizeXL : 1800,
1493
1494 fnThumbnailInit : null,
1495 fnThumbnailHoverInit : null,
1496 fnThumbnailHover : null,
1497 fnThumbnailHoverOut : null,
1498 fnThumbnailDisplayEffect : null,
1499 fnViewerInfo : null,
1500 fnImgToolbarCustInit : null,
1501 fnImgToolbarCustDisplay : null,
1502 fnImgToolbarCustClick : null,
1503 fnProcessData : null,
1504 fnThumbnailSelection : null,
1505 fnGalleryRenderStart : null,
1506 fnGalleryRenderEnd : null,
1507 fnGalleryObjectModelBuilt : null,
1508 fnGalleryLayoutApplied : null,
1509 fnThumbnailClicked : null,
1510 fnShoppingCartUpdated : null,
1511 fnThumbnailToolCustAction : null,
1512 fnThumbnailOpen : null,
1513 fnImgDisplayed : null,
1514 fnPopupMediaInfo : null,
1515
1516 i18n : {
1517 'breadcrumbHome' : 'Galleries', 'breadcrumbHome_FR' : 'Galeries',
1518 'thumbnailImageTitle' : '', 'thumbnailAlbumTitle' : '',
1519 'thumbnailImageDescription' : '', 'thumbnailAlbumDescription' : '',
1520 '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'
1521 },
1522 icons : {
1523 // example for font awesome: <i style="color:#eee;" class="fa fa-search-plus"></i>
1524 thumbnailAlbum: '<i class="nGY2Icon-folder-empty"></i>',
1525 thumbnailImage: '<i class="nGY2Icon-picture"></i>',
1526 breadcrumbAlbum: '<i class="nGY2Icon-folder-empty"></i>',
1527 breadcrumbHome: '<i class="nGY2Icon-home"></i>',
1528 breadcrumbSeparator: '<i class="nGY2Icon-left-open"></i>',
1529 breadcrumbSeparatorRtl: '<i class="nGY2Icon-right-open"></i>',
1530 navigationFilterSelected: '<i style="color:#fff;" class="nGY2Icon-ok"></i>',
1531 navigationFilterUnselected: '<i style="color:#ddd;opacity:0.3;" class="nGY2Icon-circle-empty"></i>',
1532 navigationFilterSelectedAll: '<i class="nGY2Icon-ccw"></i>',
1533 navigationPaginationPrevious: '<i class="nGY2Icon-ngy2_chevron-left"></i>',
1534 navigationPaginationNext: '<i class="nGY2Icon-ngy2_chevron-right"></i>',
1535 thumbnailSelected: '<i style="color:#bff;" class="nGY2Icon-ok-circled"></i>',
1536 thumbnailUnselected: '<i style="color:#bff;" class="nGY2Icon-circle-empty"></i>',
1537 thumbnailFeatured: '<i style="color:#dd5;" class="nGY2Icon-star"></i>',
1538 thumbnailCounter: '<i class="nGY2Icon-picture"></i>',
1539 thumbnailShare: '<i class="nGY2Icon-ngy2_share2"></i>',
1540 thumbnailDownload: '<i class="nGY2Icon-ngy2_download2"></i>',
1541 thumbnailInfo: '<i class="nGY2Icon-ngy2_info2"></i>',
1542 thumbnailShoppingcart: '<i class="nGY2Icon-basket"></i>',
1543 thumbnailDisplay: '<i class="nGY2Icon-resize-full"></i>',
1544 thumbnailCustomTool1: 'T1',
1545 thumbnailCustomTool2: 'T2',
1546 thumbnailCustomTool3: 'T3',
1547 thumbnailCustomTool4: 'T4',
1548 thumbnailCustomTool5: 'T5',
1549 thumbnailCustomTool6: 'T6',
1550 thumbnailCustomTool7: 'T7',
1551 thumbnailCustomTool8: 'T8',
1552 thumbnailCustomTool9: 'T9',
1553 thumbnailCustomTool10: 'T10',
1554 thumbnailAlbumUp: '<i style="font-size: 3em;" class="nGY2Icon-ngy2_chevron_up2"></i>',
1555 paginationNext: '<i class="nGY2Icon-right-open"></i>',
1556 paginationPrevious: '<i class="nGY2Icon-left-open"></i>',
1557 galleryMoreButton: '<i class="nGY2Icon-picture"></i> &nbsp; <i class="nGY2Icon-right-open"></i>',
1558 buttonClose: '<i class="nGY2Icon-ngy2_close2"></i>',
1559 viewerPrevious: '<i class="nGY2Icon-ngy2_chevron-left"></i>',
1560 viewerNext: '<i class="nGY2Icon-ngy2_chevron-right"></i>',
1561 viewerImgPrevious: '<i class="nGY2Icon-ngy2_chevron_left3"></i>',
1562 viewerImgNext: '<i class="nGY2Icon-ngy2_chevron_right3"></i>',
1563 viewerDownload: '<i class="nGY2Icon-ngy2_download2"></i>',
1564 viewerToolbarMin: '<i class="nGY2Icon-ellipsis-vert"></i>',
1565 viewerToolbarStd: '<i class="nGY2Icon-menu"></i>',
1566 viewerPlay: '<i class="nGY2Icon-play"></i>',
1567 viewerPause: '<i class="nGY2Icon-pause"></i>',
1568 viewerFullscreenOn: '<i class="nGY2Icon-resize-full"></i>',
1569 viewerFullscreenOff: '<i class="nGY2Icon-resize-small"></i>',
1570 viewerZoomIn: '<i class="nGY2Icon-ngy2_zoom_in2"></i>',
1571 viewerZoomOut: '<i class="nGY2Icon-ngy2_zoom_out2"></i>',
1572 viewerLinkOriginal: '<i class="nGY2Icon-ngy2_external2"></i>',
1573 viewerInfo: '<i class="nGY2Icon-ngy2_info2"></i>',
1574 viewerShare: '<i class="nGY2Icon-ngy2_share2"></i>',
1575 viewerRotateLeft: '<i class="nGY2Icon-ccw"></i>',
1576 viewerRotateRight: '<i class="nGY2Icon-cw"></i>',
1577 viewerShoppingcart: '<i class="nGY2Icon-basket"></i>',
1578 user: '<i class="nGY2Icon-user"></i>',
1579 location: '<i class="nGY2Icon-location"></i>',
1580 picture: '<i class="nGY2Icon-picture"></i>',
1581 config: '<i class="nGY2Icon-wrench"></i>',
1582 shareFacebook: '<i style="color:#3b5998;" class="nGY2Icon-facebook-squared"></i>',
1583 shareTwitter: '<i style="color:#00aced;" class="nGY2Icon-twitter-squared"></i>',
1584 // shareGooglePlus: '<i style="color:#dd4b39;" class="nGY2Icon-gplus-squared"></i>',
1585 shareTumblr: '<i style="color:#32506d;" class="nGY2Icon-tumblr-squared"></i>',
1586 sharePinterest: '<i style="color:#cb2027;" class="nGY2Icon-pinterest-squared"></i>',
1587 shareVK: '<i style="color:#3b5998;" class="nGY2Icon-vkontakte"></i>',
1588 shareMail: '<i style="color:#555;" class="nGY2Icon-mail-alt"></i>',
1589 viewerCustomTool1: 'T1',
1590 viewerCustomTool2: 'T2',
1591 viewerCustomTool3: 'T3',
1592 viewerCustomTool4: 'T4',
1593 viewerCustomTool5: 'T5',
1594 viewerCustomTool6: 'T6',
1595 viewerCustomTool7: 'T7',
1596 viewerCustomTool8: 'T8',
1597 viewerCustomTool9: 'T9',
1598 viewerCustomTool10: 'T10'
1599 }
1600 };
1601
1602 jQuery.fn.nanogallery2 = function (args, option, value) {
1603
1604 if( typeof jQuery(this).data('nanogallery2data') === 'undefined'){
1605 if( args == 'destroy' ) {
1606 // command to destroy but no instance yet --> exit
1607 return;
1608 }
1609
1610 return this.each( function(){
1611 (new jQuery.nanogallery2(this, args));
1612 });
1613 }
1614 else {
1615 // no options -->
1616 // This function breaks the chain, but provides some API methods
1617 var nG2 = $(this).data('nanogallery2data').nG2;
1618
1619 // Lightbox standalone
1620 // (Another click on an already opened media)
1621 if( args !== undefined && args.lightboxStandalone === true ) {
1622 // items exist already (G.I is populated) -> just open the lightbox again
1623 nG2.LightboxReOpen();
1624 return;
1625 }
1626
1627 switch(args){
1628 case 'displayItem':
1629 nG2.DisplayItem(option);
1630 break;
1631
1632 case 'search':
1633 return( nG2.Search(option));
1634 break;
1635
1636 case 'search2':
1637 return nG2.Search2(option, value);
1638 break;
1639
1640 case 'search2Execute':
1641 return nG2.Search2Execute();
1642 break;
1643
1644 case 'refresh':
1645 nG2.Refresh();
1646 break;
1647
1648 case 'resize':
1649 nG2.Resize();
1650 break;
1651
1652 case 'instance':
1653 return nG2;
1654 break;
1655
1656 case 'data':
1657 nG2.data= {
1658 items: nG2.I,
1659 gallery: nG2.GOM,
1660 lightbox: nG2.VOM,
1661 shoppingcart: nG2.shoppingCart
1662 };
1663 return nG2.data;
1664 break;
1665
1666 case 'reload':
1667 nG2.ReloadAlbum();
1668 return $(this);
1669 break;
1670
1671 case 'itemsSelectedGet':
1672 return nG2.ItemsSelectedGet();
1673 break;
1674
1675 case 'itemsSetSelectedValue':
1676 nG2.ItemsSetSelectedValue(option, value);
1677 break;
1678
1679 case 'option':
1680 if(typeof value === 'undefined'){
1681 return nG2.Get(option);
1682 }else{
1683 nG2.Set(option,value);
1684 if( option == 'demoViewportWidth' ) {
1685 // force resize event -> for demo purposes
1686 $(window).trigger('resize');
1687 }
1688 }
1689 break;
1690
1691 case 'destroy':
1692 nG2.Destroy();
1693 $(this).removeData('nanogallery2data');
1694 break;
1695
1696 case 'shoppingCartGet':
1697 // returns the content of the shoppingcart
1698 return nG2.shoppingCart;
1699 break;
1700
1701 case 'shoppingCartUpdate':
1702 // parameters :
1703 // - option = item's ID
1704 // - value = new quantity
1705
1706 if( typeof value === 'undefined' || typeof option === 'undefined' ){
1707 return false;
1708 }
1709
1710 var item_ID = option;
1711 var new_qty = value;
1712
1713 for( var i=0; i < nG2.shoppingCart.length; i++) {
1714 if( nG2.shoppingCart[i].ID == item_ID ) {
1715
1716 // updates counter
1717 nG2.shoppingCart[i].qty = new_qty;
1718
1719 let item = nG2.I[nG2.shoppingCart[i].idx];
1720
1721 // updates thumbnail
1722 nG2.ThumbnailToolbarOneCartUpdate( item );
1723
1724 if( new_qty == 0 ) {
1725 // removes item from shoppingcart
1726 nG2.shoppingCart.splice(i, 1);
1727 }
1728
1729 var fu = nG2.O.fnShoppingCartUpdated;
1730 if( fu !== null ) {
1731 typeof fu == 'function' ? fu(nG2.shoppingCart, item, 'api') : window[fu](nG2.shoppingCart, item, 'api');
1732 }
1733
1734 break;
1735 }
1736 }
1737
1738 return nG2.shoppingCart;
1739 break;
1740
1741 case 'shoppingCartRemove':
1742 // parameters :
1743 // - option = item's ID
1744 if( typeof option === 'undefined' ){
1745 return false;
1746 }
1747 var ID = option;
1748 for( var i=0; i < nG2.shoppingCart.length; i++) {
1749 if( nG2.shoppingCart[i].ID == ID ) {
1750
1751 var item = nG2.I[nG2.shoppingCart[i].idx];
1752
1753 // updates thumbnail
1754 nG2.shoppingCart[i].qty = 0;
1755 nG2.ThumbnailToolbarOneCartUpdate( item );
1756
1757 // removes item from shoppingcart
1758 nG2.shoppingCart.splice(i, 1);
1759
1760
1761 var fu = nG2.O.fnShoppingCartUpdated;
1762 if( fu !== null ) {
1763 typeof fu == 'function' ? fu(nG2.shoppingCart, item, 'api') : window[fu](nG2.shoppingCart, item, 'api');
1764 }
1765
1766 break;
1767 }
1768 }
1769
1770 return nG2.shoppingCart;
1771 break;
1772
1773 case 'closeViewer':
1774 nG2.CloseViewer();
1775 break;
1776 case 'minimizeToolbar':
1777 nG2.MinimizeToolbar();
1778 break;
1779 case 'maximizeToolbar':
1780 nG2.MaximizeToolbar();
1781 break;
1782 case 'paginationPreviousPage':
1783 nG2.PaginationPreviousPage();
1784 break;
1785 case 'paginationNextPage':
1786 nG2.paginationNextPage();
1787 break;
1788 case 'paginationGotoPage':
1789 nG2.PaginationGotoPage( option );
1790 break;
1791 case 'paginationCountPages':
1792 nG2.PaginationCountPages();
1793 break;
1794
1795 }
1796 return $(this);
1797
1798 }
1799 };
1800
1801
1802 // ###############################
1803 // ##### nanogallery2 script #####
1804 // ###############################
1805
1806 /** @function nanoGALLERY2 */
1807 function nanoGALLERY2() {
1808 "use strict";
1809
1810 /**
1811 * Force reload the current album, if provided by Json
1812 */
1813 this.LightboxReOpen = function(){
1814 LightboxStandaloneDisplay();
1815 }
1816
1817 /**
1818 * Force reload the current album, if provided by Json
1819 */
1820 this.ReloadAlbum = function(){
1821 if( G.O.kind === '' ) {
1822 throw 'Not supported for this content source:' + G.O.kind;
1823 }
1824
1825 var albumIdx = G.GOM.albumIdx;
1826 if( albumIdx == -1 ) {
1827 throw ('Current album not found.');
1828 }
1829
1830 var albumID = G.I[albumIdx].GetID();
1831
1832 // unselect everything & remove link to album (=logical delete)
1833 var l = G.I.length;
1834 for( var i = 0; i < l ; i++ ) {
1835 var item = G.I[i];
1836 if( item.albumID == albumID ) {
1837 item.selected = false;
1838 }
1839 }
1840
1841 G.I[albumIdx].contentIsLoaded = false;
1842
1843 DisplayAlbum('-1', albumID);
1844 };
1845
1846 /**
1847 * Set one or several items selected/unselected
1848 * @param {array} items
1849 */
1850 this.ItemsSetSelectedValue = function(items, value){
1851 var l = items.length;
1852 for( var j = 0; j < l ; j++) {
1853 ThumbnailSelectionSet(items[j], value);
1854 }
1855 };
1856
1857 /**
1858 * Returns an array of selected items
1859 * @returns {Array}
1860 */
1861 this.ItemsSelectedGet = function(){
1862 var selectedItems = [];
1863 var l = G.I.length;
1864 for( var i = 0; i < l ; i++ ) {
1865 if( G.I[i].selected == true ) {
1866 selectedItems.push(G.I[i]);
1867 }
1868 }
1869 return selectedItems;
1870 };
1871
1872 /**
1873 * Returns the value of an option
1874 * @param {string} option
1875 * @returns {nanoGALLERY.G.O}
1876 */
1877 this.Get = function(option){
1878 return G.O[option];
1879 };
1880
1881 /**
1882 * Set a new value for a defined option
1883 * @param {string} option
1884 */
1885 this.Set = function(option, value){
1886 G.O[option] = value;
1887 switch( option ) {
1888 case 'thumbnailSelectable':
1889 ThumbnailSelectionClear();
1890 // refresh the displayed gallery
1891 GalleryRender( G.GOM.albumIdx );
1892 break;
1893 }
1894 };
1895
1896 /**
1897 * Refresh the current gallery
1898 */
1899 this.Refresh = function() {
1900 // Refresh the displayed gallery
1901 GalleryRender( G.GOM.albumIdx );
1902 };
1903 /**
1904 * Resize the current gallery
1905 */
1906 this.Resize = function() {
1907 // resize the displayed gallery
1908 GalleryResize();
1909 };
1910
1911 /**
1912 * display one item (image or gallery)
1913 * itemID syntax:
1914 * - albumID --> display one album
1915 * - albumID/imageID --> display one image
1916 */
1917 this.DisplayItem = function( itemID ) {
1918 var IDs=parseIDs( itemID );
1919 if( IDs.imageID != '0' ) {
1920 DisplayPhoto( IDs.imageID, IDs.albumID );
1921 }
1922 else {
1923 DisplayAlbum( '-1', IDs.albumID );
1924 }
1925 };
1926
1927 this.ThumbnailToolbarOneCartUpdate = function ( item ) {
1928 ThumbnailBuildToolbarOneCartUpdate( item );
1929 }
1930
1931
1932
1933 var CountItemsToDisplay = function( gIdx ) {
1934 if( G.I[gIdx] == undefined ) { return 0; }
1935 var albumID = G.I[gIdx].GetID();
1936 var l = G.I.length;
1937 var cnt = 0;
1938 for( var idx = 0; idx < l; idx++ ) {
1939 var item = G.I[idx];
1940 if( item.isToDisplay(albumID) ) {
1941 cnt++;
1942 }
1943 }
1944 return cnt;
1945 }
1946 /**
1947 * Search in the displayed gallery (in thumbnails title)
1948 */
1949 this.Search = function( search ) {
1950 G.GOM.albumSearch = search.toUpperCase();
1951 var gIdx = G.GOM.albumIdx;
1952 GalleryRender( G.GOM.albumIdx );
1953 return CountItemsToDisplay( gIdx );
1954 };
1955
1956 /**
1957 * Search2 in title and tags - set search values
1958 */
1959 this.Search2 = function( searchTitle, searchTags ) {
1960 if( searchTitle != undefined && searchTitle != null ) {
1961 G.GOM.albumSearch = searchTitle.toUpperCase().trim();
1962 }
1963 else {
1964 G.GOM.albumSearch = '';
1965 }
1966
1967 if( searchTags != null && searchTags != undefined ) {
1968 G.GOM.albumSearchTags = searchTags.toUpperCase().trim();
1969 }
1970 else {
1971 G.GOM.albumSearchTags = '';
1972 }
1973 return CountItemsToDisplay( G.GOM.albumIdx );
1974 };
1975
1976 /**
1977 * Search2 - execute the search on title and tags
1978 */
1979 this.Search2Execute = function() {
1980 var gIdx = G.GOM.albumIdx;
1981 GalleryRender( G.GOM.albumIdx );
1982 return CountItemsToDisplay( gIdx );
1983 };
1984
1985
1986 /**
1987 * Destroy the current gallery
1988 */
1989 this.Destroy = function(){
1990
1991 if( G.GOM.hammertime != null ) {
1992 G.GOM.hammertime.destroy();
1993 G.GOM.hammertime = null;
1994 }
1995
1996 if( G.VOM.hammertime != null ) {
1997 G.VOM.hammertime.destroy();
1998 G.VOM.hammertime = null;
1999 }
2000
2001 // color scheme
2002 $('#ngycs_' + G.baseEltID).remove()
2003
2004 G.GOM.items = [];
2005 NGY2Item.New( G, G.i18nTranslations.breadcrumbHome, '', '0', '-1', 'album' );
2006 G.GOM.navigationBar.$newContent = null;
2007 G.$E.base.empty();
2008 G.$E.base.removeData();
2009 if( G.O.locationHash ) {
2010 jQuery(window).off('hashchange.nanogallery2.' + G.baseEltID);
2011 }
2012
2013 jQuery(window).off('resize.nanogallery2.' + G.baseEltID);
2014 jQuery(window).off('orientationChange.nanogallery2.' + G.baseEltID);
2015 jQuery(window).off('scroll.nanogallery2.' + G.baseEltID);
2016 if( G.$E.scrollableParent !== null ) {
2017 G.$E.scrollableParent.off('scroll.nanogallery2.' + G.baseEltID);
2018 }
2019 G.GOM.firstDisplay = true;
2020 };
2021
2022 /**
2023 * CloseViewer - close the media viewer
2024 */
2025 this.CloseViewer = function() {
2026 LightboxClose(null);
2027 return false;
2028 };
2029
2030 /**
2031 * MinimizeToolbar - display the minimized lightbox main toolbar
2032 */
2033 this.MinimizeToolbar = function() {
2034 ViewerToolbarForVisibilityMin();
2035 return false;
2036 };
2037
2038 /**
2039 * MaximizeToolbar - display the maximized/standard lightbox main toolbar
2040 */
2041 this.MaximizeToolbar = function() {
2042 ViewerToolbarForVisibilityStd();
2043 return false;
2044 };
2045
2046 /**
2047 * PaginationPreviousPage - gallery paginate to previous page
2048 */
2049 this.PaginationPreviousPage = function() {
2050 paginationPreviousPage();
2051 return false;
2052 };
2053
2054
2055 /**
2056 * PaginationNextPage - gallery paginate to next page
2057 */
2058 this.PaginationNextPage = function() {
2059 paginationNextPage();
2060 return false;
2061 };
2062
2063
2064 /**
2065 * PaginationGotoPage - gallery paginate to specific page
2066 */
2067 this.PaginationGotoPage = function( page ) {
2068 // var aIdx = G.$E.conPagin.data('galleryIdx');
2069 if( page > 1 ) { page--; }
2070 G.GOM.pagination.currentPage = page;
2071
2072 // scroll to top of gallery if not displayed
2073 G.GOM.ScrollToTop();
2074
2075 GalleryDisplayPart1();
2076 GalleryDisplayPart2( true );
2077 return false;
2078 };
2079
2080 /**
2081 * PaginationCountPages - gallery pagination - returns the number of pages
2082 */
2083 this.PaginationCountPages = function() {
2084 if( G.GOM.items.length == 0 ) { return 0; } // no thumbnail to display
2085
2086 var nbPages = Math.ceil((G.GOM.items[G.GOM.items.length - 1].row + 1) / G.galleryMaxRows.Get());
2087 return nbPages;
2088 };
2089
2090 /**
2091 * PaginationCountPages - gallery pagination - returns the number of pages
2092 */
2093
2094
2095
2096 // throttle()
2097 // author: underscore.js - http://underscorejs.org/docs/underscore.html
2098 // Returns a function, that, when invoked, will only be triggered at most once during a given window of time.
2099 // Normally, the throttled function will run as much as it can, without ever going more than once per wait duration;
2100 // but if you�d like to disable the execution on the leading edge, pass {leading: false}.
2101 // To disable execution on the trailing edge, ditto.
2102 var throttle = function(func, wait, options) {
2103 var context, args, result;
2104 var timeout = null;
2105 var previous = 0;
2106 if (!options) options = {};
2107 var later = function() {
2108 previous = options.leading === false ? 0 : new Date().getTime();
2109 timeout = null;
2110 result = func.apply(context, args);
2111 if (!timeout) context = args = null;
2112 };
2113 return function() {
2114 var now = new Date().getTime();
2115 if (!previous && options.leading === false) previous = now;
2116 var remaining = wait - (now - previous);
2117 context = this;
2118 args = arguments;
2119 if (remaining <= 0 || remaining > wait) {
2120 if (timeout) {
2121 clearTimeout(timeout);
2122 timeout = null;
2123 }
2124 previous = now;
2125 result = func.apply(context, args);
2126 if (!timeout) context = args = null;
2127 } else if (!timeout && options.trailing !== false) {
2128 timeout = setTimeout(later, remaining);
2129 }
2130 return result;
2131 };
2132 };
2133
2134
2135 // DEBOUNCE
2136 // author: John Hann - http://unscriptable.com/2009/03/20/debouncing-javascript-methods/
2137 // execAsap - false means executing at the end of the detection period
2138 var debounce = function (func, threshold, execAsap) {
2139 var timeout;
2140 return function debounced () {
2141 var obj = this, args = arguments;
2142 function delayed () {
2143 if (!execAsap)
2144 func.apply(obj, args);
2145 timeout = null;
2146 };
2147
2148 if (timeout)
2149 clearTimeout(timeout);
2150 // clearRequestTimeout(timeout);
2151 else if (execAsap)
2152 func.apply(obj, args);
2153 timeout = setTimeout(delayed, threshold || 100);
2154 // timeout = requestTimeout(delayed, threshold || 100);
2155 };
2156 }
2157
2158
2159 // Double requestAnimationFrame
2160 window.ng_draf = function (cb) {
2161 return requestAnimationFrame(function() {
2162 window.requestAnimationFrame(cb)
2163 })
2164 }
2165
2166 // REQUESTTIMEOUT - replace SETTIMEOUT - https://gist.github.com/joelambert/1002116
2167 /**
2168 * Behaves the same as setTimeout except uses requestAnimationFrame() where possible for better performance
2169 * @param {function} fn The callback function
2170 * @param {int} delay The delay in milliseconds
2171 */
2172
2173 window.requestTimeout = function(fn, delay) {
2174 if( !window.requestAnimationFrame &&
2175 !window.webkitRequestAnimationFrame &&
2176 !(window.mozRequestAnimationFrame && window.mozCancelRequestAnimationFrame) && // Firefox 5 ships without cancel support
2177 !window.oRequestAnimationFrame &&
2178 !window.msRequestAnimationFrame)
2179 return window.setTimeout(fn, delay);
2180
2181 var start = new Date().getTime(),
2182 handle = new Object();
2183
2184 function loop(){
2185 var current = new Date().getTime(),
2186 delta = current - start;
2187 // delta = delay;
2188
2189 delta >= delay ? fn.call() : handle.value = requestAnimFrame(loop);
2190 };
2191
2192 handle.value = requestAnimFrame(loop);
2193 return handle;
2194 };
2195
2196
2197 // requestAnimationFrame() shim by Paul Irish
2198 // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
2199 window.requestAnimFrame = (function() {
2200 return window.requestAnimationFrame ||
2201 window.webkitRequestAnimationFrame ||
2202 window.mozRequestAnimationFrame ||
2203 window.oRequestAnimationFrame ||
2204 window.msRequestAnimationFrame ||
2205 function(/* function */ callback, /* DOMElement */ element){
2206 window.setTimeout(callback, 1000 / 60);
2207 };
2208 })();
2209
2210
2211 // CLEARREQUESTTIMEOUT - to replace CLEARTIMEOUT - https://gist.github.com/joelambert/1002116
2212 /**
2213 * Behaves the same as clearTimeout except uses cancelRequestAnimationFrame() where possible for better performance
2214 * @param {int|object} fn The callback function
2215 */
2216 window.clearRequestTimeout = function(handle) {
2217 window.cancelAnimationFrame ? window.cancelAnimationFrame(handle.value) :
2218 window.webkitCancelAnimationFrame ? window.webkitCancelAnimationFrame(handle.value) :
2219 window.webkitCancelRequestAnimationFrame ? window.webkitCancelRequestAnimationFrame(handle.value) : /* Support for legacy API */
2220 window.mozCancelRequestAnimationFrame ? window.mozCancelRequestAnimationFrame(handle.value) :
2221 window.oCancelRequestAnimationFrame ? window.oCancelRequestAnimationFrame(handle.value) :
2222 window.msCancelRequestAnimationFrame ? window.msCancelRequestAnimationFrame(handle.value) :
2223 clearTimeout(handle);
2224 };
2225
2226
2227
2228 /*
2229 ** Global data for this nanogallery2 instance
2230 **/
2231 var G=this;
2232 G.I = []; // gallery items
2233 G.Id = []; // gallery items
2234 G.O = null; // user options
2235 G.baseEltID = null; // ID of the base element
2236 G.$E = {
2237 base: null, // base element
2238 conTnParent: null, // $g_containerThumbnailsParent
2239 conLoadingB: null, // loading bar - nanoGalleryLBarOff
2240 conConsole: null, // console for error messages
2241 conNavigationBar: null, // gallery navigation bar
2242 conTnBottom: null, // container on the bottom of the gallery
2243 scrollableParent: null // first scrollable parent container
2244 };
2245 G.shoppingCart = [];
2246 G.layout = { // Layout informations
2247 internal : true,
2248 engine : '',
2249 support : { rows: false },
2250 prerequisite : { imageSize: false },
2251 SetEngine: function() {
2252
2253 if( G.layout.internal ) {
2254 if( G.tn.settings.width[G.GOM.curNavLevel][G.GOM.curWidth] == 'auto' || G.tn.settings.width[G.GOM.curNavLevel][G.GOM.curWidth] == '' ) {
2255 // do not use getH() / getW() here!
2256 G.layout.engine = 'JUSTIFIED';
2257 G.layout.support.rows = true;
2258 G.layout.prerequisite.imageSize = true;
2259 return;
2260 }
2261 if( G.tn.settings.height[G.GOM.curNavLevel][G.GOM.curWidth] == 'auto' || G.tn.settings.height[G.GOM.curNavLevel][G.GOM.curWidth] == '' ) {
2262 // do not use getH() / getW() here!
2263 G.layout.engine = 'CASCADING';
2264 G.layout.support.rows = false;
2265 G.layout.prerequisite.imageSize = true;
2266 return;
2267 }
2268
2269 if( G.tn.settings.getMosaic() != null ) {
2270 G.layout.engine = 'MOSAIC';
2271 G.layout.support.rows = true;
2272 G.layout.prerequisite.imageSize = false;
2273 return;
2274 }
2275
2276 G.layout.engine = 'GRID';
2277 G.layout.support.rows=true;
2278 // if( G.tn.opt.Get('crop') === true ) {
2279 // G.layout.prerequisite.imageSize = true;
2280 // }
2281 // else {
2282 G.layout.prerequisite.imageSize = false;
2283 // }
2284 }
2285 }
2286 };
2287 G.galleryResizeEventEnabled = false;
2288 G.galleryMaxRows = { l1: 0, lN: 0,
2289 Get: function() {
2290 return G.galleryMaxRows[G.GOM.curNavLevel];
2291 }
2292 };
2293 G.galleryMaxItems = { l1: 0, lN: 0,
2294 Get: function() {
2295 return G.galleryMaxItems[G.GOM.curNavLevel];
2296 }
2297 };
2298 G.galleryFilterTags = { l1: 0, lN: 0,
2299 Get: function() {
2300 return G.galleryFilterTags[G.GOM.curNavLevel];
2301 }
2302 };
2303 G.galleryFilterTagsMode = { l1: 0, lN: 0,
2304 Get: function() {
2305 return G.galleryFilterTagsMode[G.GOM.curNavLevel];
2306 }
2307 };
2308 G.galleryDisplayMode = { l1: 'FULLCONTENT', lN: 'FULLCONTENT',
2309 Get: function() {
2310 return G.galleryDisplayMode[G.GOM.curNavLevel];
2311 }
2312 };
2313 G.galleryLastRowFull = { l1: false, lN: false,
2314 Get: function() {
2315 return G.galleryLastRowFull[G.GOM.curNavLevel];
2316 }
2317 };
2318 G.gallerySorting = { l1: '', lN: '',
2319 Get: function() {
2320 return G.gallerySorting[G.GOM.curNavLevel];
2321 }
2322 };
2323 G.galleryDisplayTransition = { l1: 'none', lN: 'none',
2324 Get: function() {
2325 return G.galleryDisplayTransition[G.GOM.curNavLevel];
2326 }
2327 };
2328 G.galleryDisplayTransitionDuration = { l1: 500, lN: 500,
2329 Get: function() {
2330 return G.galleryDisplayTransitionDuration[G.GOM.curNavLevel];
2331 }
2332 };
2333 G.$currentTouchedThumbnail = null;
2334
2335
2336
2337
2338 // ##### GENERAL THUMBNAILS PROPERTIES -->
2339 G.tn = {
2340 // levell specific options
2341 opt: {
2342 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 },
2343 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 },
2344 Get: function(opt) {
2345 return G.tn.opt[G.GOM.curNavLevel][opt];
2346 }
2347 },
2348 scale: 1, // image scale depending of the hover effect
2349 labelHeight: { // in case label on bottom, otherwise always=0
2350 l1: 0, lN: 0,
2351 get: function() {
2352 return G.tn.labelHeight[G.GOM.curNavLevel];
2353 }
2354 },
2355 defaultSize: { // default thumbnail size
2356 // label height is not included
2357 width: { l1 : { xs:0, sm:0, me:0, la:0, xl:0 }, lN : { xs:0, sm:0, me:0, la:0, xl:0 } },
2358 height: { l1 : { xs:0, sm:0, me:0, la:0, xl:0 }, lN : { xs:0, sm:0, me:0, la:0, xl:0 } },
2359 getWidth: function() {
2360 return G.tn.defaultSize.width[G.GOM.curNavLevel][G.GOM.curWidth];
2361 },
2362 getOuterWidth: function() { // width including border
2363 G.tn.borderWidth = G.tn.opt.Get('borderHorizontal');
2364 G.tn.borderHeight = G.tn.opt.Get('borderVertical');
2365 var w = G.tn.defaultSize.width[G.GOM.curNavLevel][G.GOM.curWidth] + G.tn.opt.Get('borderHorizontal') * 2;
2366 if( G.O.thumbnailLabel.get('position') == 'right' || G.O.thumbnailLabel.get('position') == 'left' ) {
2367 w += G.tn.defaultSize.width[G.GOM.curNavLevel][G.GOM.curWidth];
2368 }
2369 return w;
2370 },
2371 getHeight: function() {
2372 return G.tn.defaultSize.height[G.GOM.curNavLevel][G.GOM.curWidth];
2373 },
2374 getOuterHeight: function() { // height, border included
2375 return G.tn.defaultSize.height[G.GOM.curNavLevel][G.GOM.curWidth]+G.tn.opt.Get('borderVertical')*2;
2376 }
2377 },
2378 settings: { // user defined width/height of the image to display depending on the screen size
2379 width: { 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 height: { l1 : { xs:0, sm:0, me:0, la:0, xl:0, xsc:'u', smc:'u', mec:'u', lac:'u', xlc:'u' },
2382 lN : { xs:0, sm:0, me:0, la:0, xl:0, xsc:'u', smc:'u', mec:'u', lac:'u', xlc:'u' } },
2383 getH: function(l, w) {
2384 var cl = (l == undefined ? G.GOM.curNavLevel : l);
2385 var cw = (w == undefined ? G.GOM.curWidth : w);
2386 if( G.layout.engine == 'MOSAIC' ) {
2387 return this.height[cl][cw] * this.mosaic[cl+'Factor']['h'][cw];
2388 }
2389 else {
2390 return this.height[cl][cw];
2391 }
2392 },
2393 getW: function(l, w) {
2394 var cl = (l == undefined ? G.GOM.curNavLevel : l);
2395 var cw = (w == undefined ? G.GOM.curWidth : w);
2396 if( G.layout.engine == 'MOSAIC' ) {
2397 return this.width[cl][cw] * this.mosaic[cl+'Factor']['w'][cw];
2398 }
2399 else {
2400 return this.width[cl][cw];
2401 // return G.tn.settings.width[G.GOM.curNavLevel][G.GOM.curWidth];
2402 }
2403 },
2404 mosaic: { l1 : { xs: null, sm: null, me: null, la: null, xl: null },
2405 lN : { xs: null, sm: null, me: null, la: null, xl: null },
2406 l1Factor : { h :{ xs: 1, sm: 1, me: 1, la: 1, xl: 1 }, w :{ xs: 1, sm: 1, me: 1, la: 1, xl: 1 }},
2407 lNFactor : { h :{ xs: 1, sm: 1, me: 1, la: 1, xl: 1 }, w :{ xs: 1, sm: 1, me: 1, la: 1, xl: 1 }}
2408 },
2409 getMosaic: function() {
2410 return this.mosaic[G.GOM.curNavLevel][G.GOM.curWidth];
2411 },
2412 mosaicCalcFactor: function(l, w) {
2413 // retrieve max size multiplicator
2414 var maxW = 1;
2415 var maxH = 1;
2416 for( var n = 0; n < G.tn.settings.mosaic[l][w].length; n++ ) {
2417 maxW = Math.max(maxW, this.mosaic[l][w][n]['w']);
2418 maxH = Math.max(maxH, this.mosaic[l][w][n]['h']);
2419 }
2420 this.mosaic[l + 'Factor']['h'][w] = maxH;
2421 this.mosaic[l + 'Factor']['w'][w] = maxW;
2422 },
2423 gutterHeight: { l1 : { xs:0, sm:0, me:0, la:0, xl:0 }, lN : { xs:0, sm:0, me:0, la:0, xl:0 } },
2424 gutterWidth: { l1 : { xs:0, sm:0, me:0, la:0, xl:0 }, lN : { xs:0, sm:0, me:0, la:0, xl:0 } },
2425 GetResponsive: function( setting ) {
2426 return this[setting][G.GOM.curNavLevel][G.GOM.curWidth];
2427 }
2428 },
2429 // thumbnail hover effects
2430 hoverEffects : {
2431 std : [],
2432 level1: [],
2433 get: function() {
2434 if( G.GOM.curNavLevel == 'l1' && this.level1.length !== 0 ) {
2435 return this.level1;
2436 }
2437 else {
2438 return this.std;
2439 }
2440 }
2441 },
2442 // thumbnail init
2443 buildInit : {
2444 std : [],
2445 level1: [],
2446 get: function() {
2447 if( G.GOM.curNavLevel == 'l1' && this.level1.length !== 0 ) {
2448 return this.level1;
2449 }
2450 else {
2451 return this.std;
2452 }
2453 }
2454 },
2455 // thumbnail toolbars
2456 toolbar: {
2457 album : { topLeft : '', topRight: '', bottomLeft: '', bottomRight: '' },
2458 image : { topLeft : '', topRight: '', bottomLeft: '', bottomRight: '' },
2459 albumUp : { topLeft : '', topRight: '', bottomLeft: '', bottomRight: '' },
2460 get: function( item ) {
2461 return this[item.kind];
2462 },
2463 },
2464 style: {
2465 // inline CSS
2466 l1 : { label: '', title: '', desc: '' },
2467 lN : { label: '', title: '', desc: '' },
2468 getTitle : function() {
2469 return ('style="' + this[G.GOM.curNavLevel].title + '"');
2470 },
2471 getDesc : function() {
2472 return ('style="' + this[G.GOM.curNavLevel].desc + '"');
2473 },
2474 getLabel: function() {
2475 var s='style="'+ this[G.GOM.curNavLevel].label;
2476 s+= (G.O.RTL ? '"direction:RTL;"' :'');
2477 s+='"';
2478 return s;
2479 }
2480 }
2481 };
2482 G.scrollTimeOut = 0;
2483 G.i18nTranslations = {'paginationPrevious':'Previous', 'paginationNext':'Next', 'breadcrumbHome':'List of Albums', 'thumbnailImageTitle':'', 'thumbnailAlbumTitle':'', 'thumbnailImageDescription':'', 'thumbnailAlbumDescription':'' };
2484 G.emptyGif = 'data:image/gif;base64,R0lGODlhEAAQAIAAAP///////yH5BAEKAAEALAAAAAAQABAAAAIOjI+py+0Po5y02ouzPgUAOw==';
2485 G.CSStransformName = FirstSupportedPropertyName(["transform", "msTransform", "MozTransform", "WebkitTransform", "OTransform"]);
2486 // G.CSSfilterName = FirstSupportedPropertyName(["filter", "WebkitFilter"]);
2487 G.CSStransformStyle = FirstSupportedPropertyName(["transformStyle", "msTransformStyle", "MozTransformStyle", "WebkitTransformStyle", "OTransformStyle"]);
2488 G.CSSperspective = FirstSupportedPropertyName(["perspective", "msPerspective", "MozPerspective", "WebkitPerspective", "OPerspective"]);
2489 G.CSSbackfaceVisibilityName = FirstSupportedPropertyName(["backfaceVisibility", "msBackfaceVisibility", "MozBackfaceVisibility", "WebkitBackfaceVisibility", "OBackfaceVisibility"]);
2490 G.CSStransitionName = FirstSupportedPropertyName(["transition", "msTransition", "MozTransition", "WebkitTransition", "OTransition"]);
2491 G.CSSanimationName = FirstSupportedPropertyName(["animation", "msAnimation", "MozAnimation", "WebkitAnimation", "OAnimation"]);
2492 G.GalleryResizeThrottled = throttle(GalleryResize, 15, {leading: false});
2493
2494 G.blockList = null; // album names - block list
2495 G.allowList = null; // album names - allow list
2496 G.albumList = []; // album list
2497 G.locationHashLastUsed = '';
2498 G.custGlobals = {};
2499 G.touchAutoOpenDelayTimerID = 0;
2500 G.i18nLang = '';
2501 G.timeLastTouchStart = 0;
2502 G.custGlobals = {};
2503 G.markupOrApiProcessed = false;
2504
2505 //------------------------
2506 //--- Gallery Object Model
2507 G.GOM = {
2508 albumIdx : -1, // index (in G.I) of the currently displayed album
2509 clipArea : { top: 0, height: 0 }, // area of the GOM to display on screen
2510 displayArea : { width: 0 , height: 0 }, // size of the GOM area (=used area, not available area)
2511 displayAreaLast : { width: 0 , height: 0 }, // previous size of the GOM area
2512 displayedMoreSteps : 0, // current number of displayed steps (moreButton mode)
2513 items: [], // current items of the GOMS
2514 $imgPreloader: [],
2515 thumbnails2Display: [],
2516 itemsDisplayed : 0, // number of currently displayed thumbnails
2517 firstDisplay : true,
2518 firstDisplayTime : 0, // in conjunction with galleryRenderDelay
2519 navigationBar : { // content of the navigation bar (for breadcrumb, filter tags and next/previous pagination)
2520 displayed: false,
2521 $newContent: ''
2522 },
2523 cache : { // cached data
2524 viewport: null,
2525 containerOffset: null,
2526 areaWidth: 100 // available area width
2527 },
2528 nbSelected : 0, // number of selected items
2529 pagination : { currentPage: 0 }, // pagination data
2530 panThreshold: 60, // threshold value (in pixels) to block horizontal pan/swipe
2531 panYOnly: false, // threshold value reach -> definitively block horizontal pan until end of pan
2532 lastFullRow : -1, // number of the last row without holes
2533 lastDisplayedIdx: -1, // used to display the counter of not displayed items
2534 displayInterval : { from: 0, len: 0 },
2535 hammertime: null,
2536 curNavLevel: 'l1', // current navigation level (l1 or LN)
2537 curWidth: 'me',
2538 albumSearch: '', // current search string -> title (used to filter the thumbnails on screen)
2539 albumSearchTags: '', // current search string -> tags
2540 lastZIndex: 0, // used to put a thumbnail on top of all others (for exemple for scale hover effect)
2541 lastRandomValue: 0,
2542 slider : { // slider on last thumbnail
2543 hostIdx: -1, // idx of the thumbnail hosting the slider
2544 hostItem: null, // item hosting the slider
2545 currentIdx: 0, // idx of the current displayed item
2546 nextIdx: 0, // idx of the next item to display in the slider
2547 timerID: 0,
2548 tween: null // tranistion tween instance
2549 },
2550 NGY2Item: function( idx ) { // returns a NGY2Item or null if it does not exist
2551 if( G.GOM.items[idx] == undefined || G.GOM.items[idx] == null ) { return null; }
2552 var i = G.GOM.items[idx].thumbnailIdx;
2553 return G.I[i];
2554 },
2555 // One GOM item (thumbnail)
2556 // function GTn(index, width, height) {
2557 GTn: function(index, width, height) {
2558 this.thumbnailIdx = index;
2559 this.width = 0; // thumbnail width
2560 this.height = 0; // thumbnail height
2561 this.top = 0; // position: top
2562 this.left = 0; // position: left
2563 this.row = 0; // position: row number
2564 this.imageWidth = width; // image width
2565 this.imageHeight = height; // image height
2566 this.resizedContentWidth = 0;
2567 this.resizedContentHeight = 0;
2568 this.displayed = false;
2569 this.neverDisplayed = true;
2570 this.inDisplayArea = false;
2571 },
2572 // Position the top of the gallery to make it visible, if not displayed
2573 ScrollToTop: function() {
2574
2575 if( G.GOM.firstDisplay ) {
2576 // do no scroll to top on first display
2577 return;
2578 }
2579
2580 if( G.$E.scrollableParent === null && !topInViewportVert(G.$E.base, 20) ) {
2581 // $('html, body').animate({scrollTop: G.$E.base.offset().top}, 200);
2582 G.$E.base.get(0).scrollIntoView();
2583 }
2584
2585 if( G.$E.scrollableParent !== null ) {
2586 // gallery in a scrollable container: check if we have to scroll up so that the top of the gallery is visible
2587 // vertical position of the scrollbar
2588 var scrollTop = G.$E.scrollableParent.scrollTop();
2589 // top of the gallery relative to the top of the scrollable container
2590 var dist = Math.abs(G.$E.scrollableParent.offset().top - G.$E.base.offset().top - scrollTop);
2591 if( scrollTop > dist ) {
2592 window.ng_draf( function() {
2593 // we need a little delay before setting the new scrollbar (but why?....)
2594 G.$E.scrollableParent.scrollTop(dist);
2595 });
2596 }
2597 }
2598 }
2599 };
2600
2601
2602 //------------------------
2603 //--- Viewer Object Model
2604
2605 G.VOM = {
2606 viewerDisplayed: false, // is the viewer currently displayed
2607 viewerIsFullscreen: false, // viewer in fullscreen mode
2608 infoDisplayed: false, // is the info box displayed
2609 toolbarsDisplayed: true, // the toolbars are displayed
2610 toolsHide: null,
2611 zoom : {
2612 posX: 0, // position to center zoom in/out
2613 posY: 0,
2614 userFactor: 1, // user zoom factor (applied to the baseZoom factor)
2615 isZooming: false
2616 },
2617 padding: { H: 0, V: 0 }, // padding for the image
2618 window: { lastWidth: 0, lastHeight: 0 },
2619 $viewer: null,
2620 $toolbar: null, // viewerToolbar
2621 $toolbarTL: null, // viewer toolbar on top left
2622 $toolbarTR: null, // viewer toolbar on top right
2623
2624 toolbarMode: 'std', // current toolbar mode (standard, minimized)
2625 playSlideshow : false, // slide show mode status
2626 playSlideshowTimerID: 0, // slideshow mode time
2627 slideshowDelay: 3000, // slideshow mode - delay before next image
2628 albumID: -1,
2629 viewerMediaIsChanged: false, // media display is currently modified
2630 items: [], // current list of images to be managed by the viewer
2631
2632 panMode: 'off', // if panning, which element -> media, gallery, or zoom - if not -> off
2633
2634 $baseCont: null, // lightbox container
2635 $content: null, // pointer to the 3 media in the viewer
2636 content: {
2637 previous : {
2638 vIdx: -1,
2639 $media: null,
2640 NGY2Item: function() {
2641 return G.I[ G.VOM.items[G.VOM.content.previous.vIdx].ngy2ItemIdx ];
2642 }
2643 },
2644 current : {
2645 vIdx: -1,
2646 $media: null,
2647 NGY2Item: function() {
2648 return G.I[ G.VOM.items[G.VOM.content.current.vIdx].ngy2ItemIdx ];
2649 }
2650 },
2651 next : {
2652 vIdx: -1,
2653 $media: null,
2654 NGY2Item: function() {
2655 return G.I[ G.VOM.items[G.VOM.content.next.vIdx].ngy2ItemIdx ];
2656 }
2657 }
2658 },
2659 IdxNext: function() {
2660 var n = 0;
2661 // if( G.VOM.currItemIdx <= (G.VOM.items.length-1) ) {
2662 if( G.VOM.content.current.vIdx < (G.VOM.items.length-1) ) {
2663 n = G.VOM.content.current.vIdx + 1;
2664 }
2665 return n;
2666 },
2667 IdxPrevious: function() {
2668 var n = G.VOM.content.current.vIdx - 1;
2669 if( G.VOM.content.current.vIdx == 0 ) {
2670 n = G.VOM.items.length - 1;
2671 }
2672 return n;
2673 },
2674
2675 gallery: {
2676 $elt: null, // Base container
2677 $tmbCont: null, // Thumbnail container
2678 gwidth: 0, // thumbnail container width (all thumbnails)
2679 vwidth: 0, // visible width of the gallery (just for the visible thumbnails)
2680 oneTmbWidth: 0,
2681 firstDisplay: true,
2682 posX: 0,
2683 SetThumbnailActive() {
2684 if( G.O.viewerGallery == 'none' ) { return; }
2685 this.$tmbCont.children().removeClass('activeVThumbnail');
2686 this.$tmbCont.children().eq( G.VOM.content.current.vIdx ).addClass('activeVThumbnail');
2687 this.firstDisplay = false;
2688 },
2689 Resize: function() {
2690 if( G.O.viewerGallery == 'none' ) { return; }
2691
2692 if( !this.firstDisplay ) {
2693 var viewerW = G.VOM.$viewer.width();
2694
2695 // Center base element
2696 var maxTmb = Math.trunc(viewerW / this.oneTmbWidth); // max thumbnail that can be displayed
2697 this.vwidth = maxTmb * this.oneTmbWidth;
2698 this.$elt.css({ width: this.vwidth, left: (viewerW - this.vwidth)/2 });
2699
2700 // Set the position the thumbnails container (if there's no enough space for all thumbnails)
2701 if( G.VOM.items.length >= maxTmb ) {
2702 var tmbPos = this.oneTmbWidth * G.VOM.content.current.vIdx; // left position of the selected thumbnail
2703
2704 if( (tmbPos + this.posX) < this.vwidth ) {
2705 if( tmbPos + this.posX < 0 ) {
2706 this.posX = -tmbPos;
2707 }
2708 }
2709 else {
2710 if( tmbPos + this.posX >= this.vwidth ) {
2711 this.posX = this.vwidth - (tmbPos + this.oneTmbWidth)
2712 }
2713 }
2714 }
2715
2716 this.PanGallery(0);
2717 }
2718 else {
2719 // first display of the gallery -> opacity transition
2720 new NGTweenable().tween({
2721 from: { opacity: 0 },
2722 to: { opacity: 1 },
2723 easing: 'easeInOutSine',
2724 duration: 1000,
2725 step: function (state) {
2726 // G.VOM.gallery.$elt.css( state );
2727 },
2728 finish: function (state) {
2729 // G.VOM.gallery.$elt.css({ opacity: 1});
2730 }
2731 });
2732
2733 }
2734 },
2735 PanGallery: function( panX ){
2736
2737 // all thumbnails are visible -> center the base element
2738 if( this.gwidth < G.VOM.$viewer.width() ) { // this.oneTmbWidth
2739 this.posX = (G.VOM.$viewer.width() - this.gwidth) / 2;
2740 panX = 0; // block pan
2741 }
2742
2743 // if( this.posX > (this.vwidth - this.oneTmbWidth) ) {
2744 if( this.posX > (this.vwidth - this.oneTmbWidth) ) {
2745 // gallery is outside of the screen, right side
2746 this.posX = this.vwidth - this.oneTmbWidth;
2747 }
2748 if( (this.posX+this.gwidth) < this.oneTmbWidth ) {
2749 // gallery is outside of the screen, left side
2750 this.posX = -this.gwidth + this.oneTmbWidth;
2751 }
2752
2753 this.$tmbCont.css( G.CSStransformName , 'translateX(' + (this.posX + panX) + 'px)');
2754 },
2755 PanGalleryEnd: function( velocity ) { // velocity = pixels/millisecond
2756
2757 var d = velocity * 100; // distance
2758 new NGTweenable().tween({
2759 from: { pan: G.VOM.gallery.posX },
2760 to: { pan: G.VOM.gallery.posX + d },
2761 easing: 'easeOutQuad',
2762 duration: 500,
2763 step: function (state) {
2764 G.VOM.gallery.posX = state.pan;
2765 G.VOM.gallery.PanGallery( 0 );
2766 }
2767 });
2768 }
2769
2770 },
2771 hammertime: null, // hammer.js manager
2772 swipePosX: 0, // current horizontal swip position
2773 panPosX: 0, // position for manual pan
2774 panPosY: 0,
2775 panThreshold: 60, // threshold value (in pixels) to block vertical pan
2776 panXOnly: false, // threshold value reach -> definitively block vertical pan until end of pan
2777 singletapTime: 0,
2778 viewerTheme: '',
2779 timeImgChanged: 0,
2780 ImageLoader: {
2781 // fires a callback when image size is know (during download)
2782 // inspired by ROB - http://stackoverflow.com/users/226507/rob
2783 maxChecks: 1000,
2784 list: [],
2785 intervalHandle : null,
2786
2787 loadImage : function (callback, ngitem) {
2788 if( ngitem.mediaKind != 'img' ) { return; } // ignore - only for images
2789 var img = new Image ();
2790 img.src = ngitem.responsiveURL();
2791 if (img.width && img.height) {
2792 callback (img.width, img.height, ngitem, 0);
2793 }
2794 else {
2795 var obj = {image: img, url: ngitem.responsiveURL(), ngitem: ngitem, callback: callback, checks: 1};
2796 var i;
2797 for (i=0; i < this.list.length; i++) {
2798 if (this.list[i] == null)
2799 break;
2800 }
2801 this.list[i] = obj;
2802 if (!this.intervalHandle)
2803 this.intervalHandle = setInterval(this.interval, 50);
2804 }
2805 },
2806
2807 // called by setInterval
2808 interval : function () {
2809 var count = 0;
2810 var list = G.VOM.ImageLoader.list, item;
2811 for (var i=0; i<list.length; i++) {
2812 item = list[i];
2813 if (item != null) {
2814 if (item.image.width && item.image.height) {
2815 G.VOM.ImageLoader.list[i] = null;
2816 item.callback (item.image.width, item.image.height, item.ngitem, item.checks);
2817 }
2818 else if (item.checks > G.VOM.ImageLoader.maxChecks) {
2819 G.VOM.ImageLoader.list[i] = null;
2820 item.callback (0, 0, item.ngitem, item.checks);
2821 }
2822 else {
2823 count++;
2824 item.checks++;
2825 }
2826 }
2827 }
2828 if (count == 0) {
2829 G.VOM.ImageLoader.list = [];
2830 clearInterval (G.VOM.ImageLoader.intervalHandle);
2831 delete G.VOM.ImageLoader.intervalHandle;
2832 }
2833 }
2834 }
2835 }
2836 // One VOM item (image)
2837 function VImg( index ) {
2838 this.$e = null;
2839 this.ngy2ItemIdx = index;
2840 this.mediaNumber = G.VOM.items.length + 1;
2841 this.posX = 0; // to center the element
2842 this.posY = 0;
2843 }
2844
2845
2846 //------------------------
2847 //--- popup
2848 G.popup = {
2849 isDisplayed: false,
2850 $elt: null,
2851 close: function() {
2852 if( this.$elt != null ) {
2853 var tweenable = new NGTweenable();
2854 tweenable.tween({
2855 from: { opacity:1 },
2856 to: { opacity:0 },
2857 attachment: { t: this },
2858 easing: 'easeInOutSine',
2859 duration: 100,
2860 step: function (state, att) {
2861 if( att.t.$elt != null ) {
2862 att.t.$elt.css('opacity',state.opacity);
2863 }
2864 },
2865 finish: function (state, att) {
2866 if( att.t.$elt != null ) {
2867 att.t.$elt.remove();
2868 att.t.$elt=null;
2869 }
2870 att.t.isDisplayed=false;
2871 }
2872 });
2873 }
2874 }
2875 }
2876
2877
2878 // Color schemes - Gallery
2879 // Gradient generator: https://www.grabient.com/
2880 G.galleryTheme_dark = {
2881 navigationBar : { background: 'none', borderTop: '', borderBottom: '', borderRight: '', borderLeft: '' },
2882 navigationBreadcrumb : { background: '#111', color: '#fff', colorHover: '#ccc', borderRadius: '4px' },
2883 navigationFilter : { color: '#ddd', background: '#111', colorSelected: '#fff', backgroundSelected: '#111', borderRadius: '4px' },
2884 navigationPagination : { background: '#111', color: '#fff', colorHover: '#ccc', borderRadius: '4px' },
2885 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' },
2886 thumbnailIcon : { padding: '5px', color: '#fff', shadow:'' },
2887 pagination : { background: '#181818', backgroundSelected: '#666', color: '#fff', borderRadius: '2px', shapeBorder: '3px solid #666', shapeColor: '#444', shapeSelectedColor: '#aaa'}
2888 };
2889
2890 G.galleryTheme_light = {
2891 navigationBar : { background: 'none', borderTop: '', borderBottom: '', borderRight: '', borderLeft: '' },
2892 navigationBreadcrumb : { background: '#eee', color: '#000', colorHover: '#333', borderRadius: '4px' },
2893 navigationFilter : { background: '#eee', color: '#222', colorSelected: '#000', backgroundSelected: '#eee', borderRadius: '4px' },
2894 navigationPagination : { background: '#eee', color: '#000', colorHover: '#333', borderRadius: '4px' },
2895 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' },
2896 thumbnailIcon : { padding: '5px', color: '#fff' },
2897 pagination : { background: '#eee', backgroundSelected: '#aaa', color: '#000', borderRadius: '2px', shapeBorder: '3px solid #666', shapeColor: '#444', shapeSelectedColor: '#aaa'}
2898 };
2899
2900 // Color schemes - lightbox
2901 G.viewerTheme_dark = {
2902 background: '#000',
2903 barBackground: 'rgba(4, 4, 4, 0.2)',
2904 barBorder: '0px solid #111',
2905 barColor: '#fff',
2906 barDescriptionColor: '#ccc'
2907 };
2908 G.viewerTheme_light = {
2909 background: '#f8f8f8',
2910 barBackground: 'rgba(4, 4, 4, 0.7)',
2911 barBorder: '0px solid #111',
2912 barColor: '#fff',
2913 barDescriptionColor: '#ccc'
2914 };
2915
2916
2917
2918 // shortcut with G context to NGY2TOOLS
2919 // var NanoAlert = NGY2Tools.NanoAlert.bind(G);
2920 // var NanoConsoleLog = NGY2Tools.NanoConsoleLog.bind(G);
2921 var NanoAlert = NGY2Tools.NanoAlert;
2922 var NanoConsoleLog = NGY2Tools.NanoConsoleLog;
2923
2924
2925 /** @function initiateGallery2 */
2926 this.initiateGallery2 = function( element, params ) {
2927
2928 // GLOBAL OPTIONS
2929 G.O = params;
2930 // Base element
2931 G.$E.base = jQuery(element);
2932 G.baseEltID = G.$E.base.attr('id');
2933 if( G.baseEltID == undefined ) {
2934 // set a default ID to the root container
2935 var base_id = 'my_nanogallery';
2936 var c = '';
2937 var f = true;
2938 while( f ) {
2939 if (document.getElementById(base_id + c)) {
2940 // ID already exists
2941 if( c == '' ) {
2942 c = 1;
2943 }
2944 else {
2945 c++;
2946 }
2947 }
2948 else {
2949 f = false;
2950 G.baseEltID = 'my_nanogallery' + c;
2951 }
2952 }
2953 G.$E.base.attr('id', G.baseEltID)
2954 }
2955 G.O.$markup = [];
2956
2957 DefineVariables();
2958 SetPolyFills();
2959 BuildSkeleton();
2960 G.GOM.firstDisplayTime = Date.now();
2961
2962 SetGlobalEvents();
2963
2964 // check if only one specific album will be used
2965 if( !G.O.lightboxStandalone ) {
2966 var albumToDisplay = G.O.album;
2967 if( albumToDisplay == '' && G.O.photoset != '' ) {
2968 albumToDisplay = G.O.photoset;
2969 G.O.album = G.O.photoset;
2970 }
2971 if( albumToDisplay != '' ) {
2972 G.O.displayBreadcrumb = false; // no breadcrumb since only 1 album
2973 if( albumToDisplay.toUpperCase() != 'NONE' ) {
2974 // open a public album
2975 if( G.O.kind == "nano_photos_provider2") {
2976 if( albumToDisplay == decodeURIComponent(albumToDisplay)) {
2977 // album ID must be encoded
2978 albumToDisplay = encodeURIComponent(albumToDisplay);
2979 G.O.album = albumToDisplay;
2980 }
2981 }
2982 NGY2Item.New( G, '', '', albumToDisplay, '-1', 'album' );
2983 if( !ProcessLocationHash() ) {
2984 DisplayAlbum('-1', albumToDisplay);
2985 }
2986 return;
2987 }
2988 }
2989 }
2990
2991 // use full content
2992 // add base album
2993 NGY2Item.New( G, G.i18nTranslations.breadcrumbHome, '', '0', '-1', 'album' );
2994
2995 processStartOptions();
2996
2997 }
2998
2999
3000 /** @function processStartOptions */
3001 function processStartOptions() {
3002 // open image or album
3003 // 1. load hidden albums
3004 // 2. check if location hash set (deep linking)
3005 // 3. check openOnStart parameter
3006 // 4. open root album (ID=-1)
3007
3008 // hidden/private albums are loaded on plugin start (Picasa) --> no more available in Google Photos
3009 // if( G.albumListHidden.length > 0 ) {
3010 // jQuery.nanogallery2['data_'+G.O.kind](G, 'GetHiddenAlbums', G.albumListHidden, processStartOptionsPart2);
3011 // return;
3012 //}
3013
3014 if( !ProcessLocationHash() ) {
3015 processStartOptionsPart2();
3016 }
3017 }
3018
3019 /** @function processStartOptionsPart2 */
3020 function processStartOptionsPart2() {
3021
3022 // Check location hash + start parameters -> determine what to do on start
3023 if( G.O.lightboxStandalone ) {
3024 LightboxStandaloneFindContent();
3025 }
3026 else {
3027 // openOnStart parameter
3028 if( G.O.openOnStart != '' ) {
3029 var IDs = parseIDs(G.O.openOnStart);
3030 if( IDs.imageID != '0' ) {
3031 DisplayPhoto(IDs.imageID, IDs.albumID);
3032 }
3033 else {
3034 DisplayAlbum('-1', IDs.albumID);
3035 }
3036 }
3037 else {
3038 // open root album (ID = -1)
3039 DisplayAlbum('-1', 0);
3040 }
3041 }
3042 }
3043
3044
3045 // Lightbox standaone -> retrieve the items to display
3046 // Each item needs at least a thumbnail image and a big image
3047 // ONLY IMAGES SUPPORTED
3048 function LightboxStandaloneFindContent() {
3049
3050 G.GOM.curNavLevel = 'l1';
3051
3052 if( G.O.items == null ) {
3053 // retrieve all element having "data-nanogallery2-lightbox" and from the same group if defined
3054 var elts = jQuery('[data-nanogallery2-Lightbox');
3055 // element group
3056 var g = G.$E.base[0].dataset.nanogallery2Lgroup;
3057
3058 GetContentMarkup( elts, g );
3059 }
3060 else {
3061 // Content defined in the starting parameters
3062 GetContentApiObject();
3063 }
3064
3065 LightboxStandaloneDisplay();
3066
3067 }
3068
3069
3070 // Populate G.VOM.items + open the lightbox
3071 function LightboxStandaloneDisplay() {
3072
3073 G.VOM.items = [];
3074 G.VOM.albumID = '0';
3075 G.GOM.curNavLevel = 'l1';
3076 var vcnt = 0;
3077
3078 var elt = G.$E.base[0].attributes;
3079 var thumbsrc = '';
3080 // src attribute (img element)
3081 if( elt.hasOwnProperty('src') ) {
3082 thumbsrc = elt['src'].nodeValue;
3083 }
3084 // data-ngthumb attribute
3085 if( thumbsrc == '' && elt.hasOwnProperty('data-ngthumb') ) {
3086 thumbsrc = elt['data-ngthumb'].nodeValue;
3087 }
3088
3089 var displayIdx = undefined;
3090 for( var idx = 0; idx < G.I.length; idx++ ) {
3091 if( G.I[idx].kind == 'image' ) {
3092 var vimg = new VImg(idx);
3093 G.VOM.items.push(vimg);
3094
3095 if( G.I[idx].thumbImg().src == thumbsrc ) {
3096 // same thumbnail URL
3097 displayIdx = vcnt;
3098 }
3099 vcnt++;
3100 }
3101
3102 }
3103 if( G.VOM.items.length > 0 ) {
3104 LightboxOpen( displayIdx );
3105 }
3106 else {
3107 NanoConsoleLog(G, 'No content for Lightbox standalone.');
3108 }
3109 }
3110
3111
3112
3113 // Parse string to extract albumID and imageID (format albumID/imageID)
3114 function parseIDs( IDs ) {
3115 var r = { albumID: '0', imageID: '0' };
3116
3117 var t = IDs.split('/');
3118 if( t.length > 0 ) {
3119 r.albumID = t[0];
3120 if( t.length > 1 ) {
3121 r.imageID = t[1];
3122 }
3123 }
3124 return r;
3125 }
3126
3127
3128 /** @function DisplayAlbum */
3129 function DisplayAlbum( imageID, albumID ) {
3130 // close viewer if already displayed
3131 if( G.VOM.viewerDisplayed ) {
3132 LightboxClose(null);
3133 }
3134
3135 // set current navigation level (l1 or lN)
3136 var albumIdx = NGY2Item.GetIdx(G, albumID);
3137 G.GOM.curNavLevel = 'lN';
3138 if( albumIdx == 0 ) {
3139 G.GOM.curNavLevel = 'l1';
3140 }
3141 G.layout.SetEngine();
3142 G.galleryResizeEventEnabled = false;
3143
3144 if( albumIdx == -1 ) {
3145 NGY2Item.New( G, '', '', albumID, '0', 'album' ); // create empty album
3146 albumIdx = G.I.length - 1;
3147 }
3148
3149 if( !G.I[albumIdx].contentIsLoaded ) {
3150 // get content of the album if not already loaded
3151 AlbumGetContent( albumID, DisplayAlbum, imageID, albumID );
3152 return;
3153 }
3154
3155 ThumbnailSelectionClear();
3156
3157 G.GOM.pagination.currentPage = 0;
3158 SetLocationHash( albumID, '' );
3159 GalleryRender( albumIdx );
3160
3161 }
3162
3163
3164 //----- manage the bottom area of the gallery -> "pagination" or "more button"
3165 function GalleryBottomManage() {
3166
3167 switch( G.galleryDisplayMode.Get() ) {
3168 case 'PAGINATION':
3169 if( G.layout.support.rows && G.galleryMaxRows.Get() > 0 ) {
3170 ManagePagination();
3171 }
3172 break;
3173 case 'MOREBUTTON':
3174 G.$E.conTnBottom.off('click');
3175 var nb = G.GOM.items.length-G.GOM.itemsDisplayed;
3176 if( nb == 0 ) {
3177 G.$E.conTnBottom.empty();
3178 }
3179 else {
3180 G.$E.conTnBottom.html('<div class="nGY2GalleryMoreButton"><div class="nGY2GalleryMoreButtonAnnotation">+'+nb+' ' + G.O.icons.galleryMoreButton +'</div></div>');
3181 G.$E.conTnBottom.on('click', function(e) {
3182 G.GOM.displayedMoreSteps++;
3183 GalleryResize();
3184 });
3185 }
3186 break;
3187 case 'FULLCONTENT':
3188 default:
3189 break;
3190 }
3191 }
3192
3193
3194 // add one album/folder to the breadcrumb
3195 function breadcrumbAdd( albumIdx ) {
3196
3197 var ic='';
3198 if( !G.O.breadcrumbHideIcons ) {
3199 ic=G.O.icons.breadcrumbAlbum;
3200 if( albumIdx == 0 ) {
3201 ic=G.O.icons.breadcrumbHome;
3202 }
3203 }
3204 var $newDiv =jQuery('<div class="oneItem">'+ic + G.I[albumIdx].title+'</div>').appendTo(G.GOM.navigationBar.$newContent.find('.nGY2Breadcrumb'));
3205 if( G.O.breadcrumbOnlyCurrentLevel ) {
3206 // link to parent folder (only 1 level is displayed in the breadcrumb)
3207 if( albumIdx == 0 ) {
3208 // no parent level -> stay on current one
3209 jQuery($newDiv).data('albumID','0');
3210 }
3211 else {
3212 jQuery($newDiv).data('albumID',G.I[albumIdx].albumID);
3213 }
3214 }
3215 else {
3216 // link to current folder
3217 jQuery($newDiv).data('albumID',G.I[albumIdx].GetID());
3218 }
3219 $newDiv.click(function() {
3220 var cAlbumID = jQuery(this).data('albumID');
3221 DisplayAlbum('-1', cAlbumID);
3222 return;
3223 });
3224 }
3225
3226 // add one separator to breadcrumb
3227 function breadcrumbAddSeparator( lastAlbumID ) {
3228 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'));
3229 jQuery($newSep).data('albumIdx',lastAlbumID);
3230 $newSep.click(function() {
3231 var sepAlbumIdx=jQuery(this).data('albumIdx');
3232 DisplayAlbum('-1', G.I[sepAlbumIdx].GetID());
3233 return;
3234 });
3235 }
3236
3237
3238
3239 // Manage the gallery toolbar (breadcrumb + tag filter + pagination next/previous)
3240 function GalleryNavigationBar( albumIdx ) {
3241
3242 // Title + background image
3243 // var bgImage='';
3244 // var l=G.I.length;
3245 // var albumID = G.I[albumIdx].GetID();
3246 // for( var idx=0; idx<l ; idx++) {
3247 // var item=G.I[idx];
3248 // if( item.kind == 'image' && item.isToDisplay(albumID) ) {
3249 // 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>';
3250 // break;
3251 // }
3252 // }
3253
3254 //console.log(bgImage);
3255
3256 // new navigation bar items are not build in the DOM, but in memory
3257 G.GOM.navigationBar.$newContent=jQuery('<div class="nGY2Navigationbar"></div>');
3258 //G.GOM.navigationBar.$newContent = jQuery(bgImage );
3259 //console.log(G.GOM.navigationBar.$newContent);
3260
3261 //-- manage breadcrumb
3262 if( G.O.displayBreadcrumb == true && !G.O.thumbnailAlbumDisplayImage) {
3263 // retrieve new folder level
3264 var newLevel = 0,
3265 lstItems=[];
3266 if( albumIdx != 0 ) {
3267 var l=G.I.length,
3268 parentID=0;
3269
3270 lstItems.push(albumIdx);
3271 var curIdx=albumIdx;
3272 newLevel++;
3273
3274 while( G.I[curIdx].albumID != 0 && G.I[curIdx].albumID != -1) {
3275 for(var i=1; i < l; i++ ) {
3276 if( G.I[i].GetID() == G.I[curIdx].albumID ) {
3277 curIdx=i;
3278 lstItems.push(curIdx);
3279 newLevel++;
3280 break;
3281 }
3282 }
3283 }
3284 }
3285
3286 // build breadcrumb
3287 if( !(G.O.breadcrumbAutoHideTopLevel && newLevel == 0) ) {
3288 BreadcrumbBuild( lstItems );
3289 }
3290 }
3291
3292
3293 //-- manage and build tag filters
3294 if( G.galleryFilterTags.Get() != false ) {
3295 var nTags = G.I[albumIdx].albumTagList.length;
3296 if( nTags > 0 ) {
3297 for(var i = 0; i < nTags; i++ ) {
3298 var s = G.I[albumIdx].albumTagList[i];
3299 var ic = G.O.icons.navigationFilterUnselected;
3300 var tagClass = 'Unselected';
3301 if( jQuery.inArray(s, G.I[albumIdx].albumTagListSel) >= 0 ) {
3302 tagClass = 'Selected';
3303 ic = G.O.icons.navigationFilterSelected;
3304 }
3305
3306 var $newTag = jQuery('<div class="nGY2NavigationbarItem nGY2NavFilter' + tagClass + '">'+ ic +' '+ s +'</div>').appendTo(G.GOM.navigationBar.$newContent);
3307
3308 $newTag.click(function() {
3309
3310 var $this = jQuery(this);
3311 var tag = $this.text().replace(/^\s*|\s*$/, ''); //trim trailing/leading whitespace
3312
3313 if( G.galleryFilterTagsMode.Get() == 'single' ) {
3314 // single TAG selection
3315 G.I[albumIdx].albumTagListSel = [];
3316 G.I[albumIdx].albumTagListSel.push(tag);
3317 }
3318 else {
3319 // multiple selection of TAGS
3320 // if( $this.hasClass('oneTagUnselected') ){
3321 if( $this.hasClass('nGY2NavFilterUnselected') ){
3322 G.I[albumIdx].albumTagListSel.push(tag);
3323 }
3324 else {
3325 var tidx=jQuery.inArray(tag,G.I[albumIdx].albumTagListSel);
3326 if( tidx != -1 ) {
3327 G.I[albumIdx].albumTagListSel.splice(tidx,1);
3328 }
3329 }
3330 $this.toggleClass('nGY2NavFilters-oneTagUnselected nGY2NavFilters-oneTagSelected');
3331 }
3332
3333 DisplayAlbum('-1', G.I[albumIdx].GetID());
3334 });
3335 }
3336
3337 // clear/reset TAGS selection
3338 var $newClearFilter=jQuery('<div class="nGY2NavigationbarItem nGY2NavFilterSelectAll">'+ G.O.icons.navigationFilterSelectedAll +'</div>').appendTo(G.GOM.navigationBar.$newContent);
3339 $newClearFilter.click(function() {
3340 // var nTags = G.I[albumIdx].albumTagList.length;
3341 G.I[albumIdx].albumTagListSel = [];
3342 // for(var i = 0; i < nTags; i++ ) {
3343 // var s = G.I[albumIdx].albumTagList[i];
3344 // G.I[albumIdx].albumTagListSel.push(s);
3345 // }
3346 DisplayAlbum('-1', G.I[albumIdx].GetID());
3347 });
3348 }
3349 }
3350
3351 // --- Gallery pagination next/previous
3352 if( G.galleryDisplayMode.Get() == "PAGINATION" && G.O.galleryPaginationTopButtons ) {
3353 if( G.layout.support.rows && G.galleryMaxRows.Get() > 0 ) {
3354 // ManagePagination( G.GOM.albumIdx );
3355 var $newTagPrev = jQuery('<div class="nGY2NavigationbarItem nGY2NavPagination">'+G.O.icons.navigationPaginationPrevious+'</div>').appendTo(G.GOM.navigationBar.$newContent);
3356 $newTagPrev.click(function() {
3357 paginationPreviousPage();
3358 });
3359 var $newTagNext = jQuery('<div class="nGY2NavigationbarItem nGY2NavPagination">'+G.O.icons.navigationPaginationNext+'</div>').appendTo(G.GOM.navigationBar.$newContent);
3360 $newTagNext.click(function() {
3361 paginationNextPage();
3362 });
3363 }
3364 }
3365
3366 }
3367
3368 function BreadcrumbBuild(lstItems) {
3369
3370 // console.log(G.GOM.navigationBar.$newContent);
3371 jQuery('<div class="nGY2NavigationbarItem nGY2Breadcrumb"></div>').appendTo(G.GOM.navigationBar.$newContent);
3372 // console.log(G.GOM.navigationBar.$newContent);
3373
3374 if( G.O.breadcrumbOnlyCurrentLevel ) {
3375 // display only 1 separator and the current folder level
3376 if( lstItems.length == 0 ) {
3377 breadcrumbAdd(0);
3378 }
3379 else {
3380 // var last=lstItems.length-1;
3381 if( lstItems.length == 1 ) {
3382 breadcrumbAddSeparator(0); // root level
3383 }
3384 else {
3385 breadcrumbAddSeparator(lstItems[0]);
3386 }
3387 breadcrumbAdd(lstItems[0]);
3388 }
3389 }
3390 else {
3391 // display the full breadcrum (full folder levels including root level)
3392 breadcrumbAdd(0);
3393 if( lstItems.length > 0 ) {
3394 breadcrumbAddSeparator(0);
3395 for(var i=lstItems.length-1; i>=0 ; i-- ) {
3396 breadcrumbAdd(lstItems[i]);
3397 if( i > 0 ) {
3398 breadcrumbAddSeparator(lstItems[i-1]);
3399 }
3400 }
3401 }
3402 }
3403
3404 }
3405
3406
3407 // Display gallery pagination
3408 function ManagePagination() {
3409
3410 G.$E.conTnBottom.css('opacity', 0);
3411 G.$E.conTnBottom.children().remove();
3412
3413 if( G.GOM.items.length == 0 ) { return; } // no thumbnail to display
3414
3415 // calculate the number of pages
3416 var nbPages = Math.ceil((G.GOM.items[G.GOM.items.length - 1].row + 1)/G.galleryMaxRows.Get());
3417
3418 // only one page -> do not display pagination
3419 if( nbPages == 1 ) { return; }
3420
3421 // check if current page still exists (for example after a resize)
3422 if( G.GOM.pagination.currentPage > (nbPages-1) ) {
3423 G.GOM.pagination.currentPage = nbPages-1;
3424 }
3425
3426 GalleryRenderGetInterval();
3427 // nothing to display --> exit
3428 if( G.GOM.displayInterval.len == 0 ) { return; }
3429
3430 // display "previous"
3431 if( G.O.galleryPaginationMode == 'NUMBERS' && G.GOM.pagination.currentPage > 0 ) {
3432 var $eltPrev = jQuery('<div class="nGY2PaginationPrev">'+G.O.icons.paginationPrevious+'</div>').appendTo(G.$E.conTnBottom);
3433 $eltPrev.click(function(e) {
3434 paginationPreviousPage();
3435 });
3436 }
3437
3438 var firstPage = 0;
3439 var lastPage = nbPages;
3440 if( G.O.galleryPaginationMode != 'NUMBERS' ) {
3441 // no 'previous'/'next' and no max number of pagination items
3442 firstPage = 0;
3443 }
3444 else {
3445 // display pagination numbers and previous/next
3446 // var vp = G.O.paginationVisiblePages;
3447 var numberOfPagesToDisplay = G.O.paginationVisiblePages;
3448 if( numberOfPagesToDisplay >= nbPages ) {
3449 firstPage = 0;
3450 }
3451 else {
3452 // we have more pages than we want to display
3453 var nbBeforeAfter = 0;
3454 if( isOdd(numberOfPagesToDisplay) ) {
3455 nbBeforeAfter = (numberOfPagesToDisplay + 1) / 2;
3456 }
3457 else {
3458 nbBeforeAfter = numberOfPagesToDisplay / 2;
3459 }
3460
3461 if( G.GOM.pagination.currentPage < nbBeforeAfter ) {
3462 firstPage = 0;
3463 lastPage = numberOfPagesToDisplay - 1;
3464 if( lastPage > nbPages ) {
3465 lastPage = nbPages - 1;
3466 }
3467 }
3468 else {
3469 firstPage = G.GOM.pagination.currentPage - nbBeforeAfter;
3470 lastPage = firstPage + numberOfPagesToDisplay;
3471 if( lastPage > nbPages ) {
3472 lastPage = nbPages - 1;
3473 }
3474 }
3475
3476 if( (lastPage - firstPage) < numberOfPagesToDisplay ) {
3477 firstPage = lastPage - numberOfPagesToDisplay;
3478 if( firstPage < 0 ) {
3479 firstPage = 0;
3480 }
3481 }
3482
3483 }
3484 }
3485
3486 // render pagination items
3487 for(var i = firstPage; i < lastPage; i++ ) {
3488 var c = '';
3489 var p = '';
3490
3491 switch( G.O.galleryPaginationMode ) {
3492 case 'NUMBERS':
3493 c = 'nGY2paginationItem';
3494 p = i + 1;
3495 break;
3496 case 'DOTS':
3497 c = 'nGY2paginationDot';
3498 break;
3499 case 'RECTANGLES':
3500 c = 'nGY2paginationRectangle';
3501 break;
3502 }
3503 if( i == G.GOM.pagination.currentPage ) {
3504 c += 'CurrentPage';
3505 }
3506
3507 var elt$ = jQuery('<div class="' + c + '">' + p + '</div>').appendTo(G.$E.conTnBottom);
3508 elt$.data('pageNumber', i );
3509 elt$.click( function(e) {
3510 G.GOM.pagination.currentPage = jQuery(this).data('pageNumber');
3511 TriggerCustomEvent('pageChanged');
3512
3513 // scroll to top of gallery if not displayed
3514 G.GOM.ScrollToTop();
3515
3516 GalleryDisplayPart1();
3517 GalleryDisplayPart2( true );
3518 });
3519
3520 }
3521
3522 // display "next"
3523 if( G.O.galleryPaginationMode == 'NUMBERS' && (G.GOM.pagination.currentPage + 1) < nbPages ) {
3524 var $eltNext = jQuery('<div class="nGY2PaginationNext">' + G.O.icons.paginationNext + '</div>').appendTo(G.$E.conTnBottom);
3525 $eltNext.click( function(e) {
3526 paginationNextPage();
3527 });
3528 }
3529
3530 G.$E.conTnBottom.css('opacity', 1);
3531
3532 }
3533 function isOdd(num) { return (num % 2) == 1;}
3534
3535 // pagination - next page
3536 function paginationNextPage() {
3537 // var aIdx = G.GOM.albumIdx;
3538 var n1 = 0;
3539 ThumbnailHoverOutAll();
3540
3541 // pagination - max lines per page mode
3542 if( G.galleryMaxRows.Get() > 0 ) {
3543 // number of pages
3544 n1 = (G.GOM.items[G.GOM.items.length - 1].row + 1) / G.galleryMaxRows.Get();
3545 }
3546 var n2 = Math.ceil(n1);
3547 var pn = G.GOM.pagination.currentPage;
3548 if( pn < (n2-1) ) {
3549 pn++;
3550 }
3551 else {
3552 pn = 0;
3553 }
3554
3555 G.GOM.pagination.currentPage = pn;
3556 TriggerCustomEvent('pageChanged');
3557
3558 // scroll to top of gallery if not displayed
3559 G.GOM.ScrollToTop();
3560
3561 GalleryDisplayPart1();
3562 GalleryDisplayPart2( true );
3563 }
3564
3565 // pagination - previous page
3566 function paginationPreviousPage() {
3567 // var aIdx=G.$E.conTnBottom.data('galleryIdx'),
3568 // var aIdx = G.GOM.albumIdx;
3569 var n1 = 0;
3570
3571 ThumbnailHoverOutAll();
3572
3573 // pagination - max lines per page mode
3574 if( G.galleryMaxRows.Get() > 0 ) {
3575 // number of pages
3576 n1 = (G.GOM.items[G.GOM.items.length - 1].row + 1) / G.galleryMaxRows.Get();
3577 }
3578 var n2 = Math.ceil(n1);
3579
3580 // var pn=G.$E.conTnBottom.data('currentPageNumber');
3581 var pn = G.GOM.pagination.currentPage;
3582 if( pn > 0 ) {
3583 pn--;
3584 }
3585 else {
3586 pn = n2 - 1;
3587 }
3588
3589 G.GOM.pagination.currentPage = pn;
3590 TriggerCustomEvent('pageChanged');
3591
3592 // scroll to top of gallery if not displayed
3593 G.GOM.ScrollToTop();
3594
3595 GalleryDisplayPart1();
3596 GalleryDisplayPart2( true );
3597 }
3598
3599 // retrieve the from/to intervall for gallery thumbnail render
3600 function GalleryRenderGetInterval() {
3601 G.GOM.displayInterval.from = 0;
3602 G.GOM.displayInterval.len = G.I.length;
3603
3604 switch( G.galleryDisplayMode.Get() ) {
3605 case 'PAGINATION':
3606 if( G.layout.support.rows ) {
3607 let nbTn = G.GOM.items.length;
3608 var firstRow = G.GOM.pagination.currentPage * G.galleryMaxRows.Get();
3609 var lastRow = firstRow + G.galleryMaxRows.Get();
3610 var firstTn = -1;
3611 G.GOM.displayInterval.len = 0;
3612 for( var i = 0; i < nbTn ; i++ ) {
3613 let curTn = G.GOM.items[i];
3614 if( curTn.row >= firstRow && curTn.row < lastRow ) {
3615 if( firstTn == -1 ) {
3616 G.GOM.displayInterval.from = i;
3617 firstTn = i;
3618 }
3619 G.GOM.displayInterval.len++;
3620 }
3621 }
3622 }
3623 break;
3624 case 'MOREBUTTON':
3625 if( G.layout.support.rows ) {
3626 let nbTn = G.GOM.items.length;
3627 let lastRow = G.O.galleryDisplayMoreStep * (G.GOM.displayedMoreSteps+1);
3628 G.GOM.displayInterval.len = 0;
3629 for( var i = 0; i < nbTn ; i++ ) {
3630 let curTn = G.GOM.items[i];
3631 if( curTn.row < lastRow ) {
3632 G.GOM.displayInterval.len++;
3633 }
3634 }
3635 }
3636 break;
3637 case 'ROWS':
3638 if( G.layout.support.rows ) {
3639 let nbTn = G.GOM.items.length;
3640 let lastRow = G.galleryMaxRows.Get();
3641 if( G.galleryLastRowFull.Get() && G.GOM.lastFullRow != -1 ) {
3642 if( lastRow > (G.GOM.lastFullRow + 1) ) {
3643 lastRow = G.GOM.lastFullRow + 1;
3644 }
3645 }
3646 G.GOM.displayInterval.len = 0;
3647 for( var i = 0; i < nbTn ; i++ ) {
3648 let curTn = G.GOM.items[i];
3649 if( curTn.row < lastRow ) {
3650 G.GOM.displayInterval.len++;
3651 }
3652 }
3653 }
3654 break;
3655 default:
3656 case 'FULLCONTENT':
3657 if( G.layout.support.rows && G.galleryLastRowFull.Get() && G.GOM.lastFullRow != -1 ) {
3658 let nbTn = G.GOM.items.length;
3659 let lastRow = G.GOM.lastFullRow + 1;
3660 G.GOM.displayInterval.len = 0;
3661 for( var i = 0; i < nbTn ; i++ ) {
3662 let curTn = G.GOM.items[i];
3663 if( curTn.row < lastRow ) {
3664 G.GOM.displayInterval.len++;
3665 }
3666 }
3667 }
3668 break;
3669 }
3670 }
3671
3672
3673 // RENDER THE GALLERY
3674 function GalleryRender( albumIdx ) {
3675 TriggerCustomEvent('galleryRenderStart');
3676 clearTimeout(G.GOM.slider.timerID);
3677 G.GOM.slider.hostIdx = -1; // disabled slider on thumbnail
3678
3679 var fu=G.O.fnGalleryRenderStart;
3680 if( fu !== null ) {
3681 // typeof fu == 'function' ? fu(albumIdx) : window[fu](albumIdx);
3682 typeof fu == 'function' ? fu( G.I[G.GOM.albumIdx] ) : window[fu]( G.I[G.GOM.albumIdx] );
3683 }
3684
3685 G.layout.SetEngine();
3686 G.galleryResizeEventEnabled = false;
3687 G.GOM.albumIdx = -1;
3688 G.GOM.lastDisplayedIdx = -1;
3689
3690 // pagination
3691 if( G.$E.conTnBottom !== undefined ) {
3692 // G.$E.conTnBottom.children().remove();
3693 G.$E.conTnBottom.empty();
3694 }
3695
3696 // navigation toolbar (breadcrumb + tag filters + pagination next/previous)
3697 GalleryNavigationBar(albumIdx);
3698
3699 if( G.GOM.firstDisplay ) {
3700 // first gallery display
3701 G.GOM.firstDisplay = false;
3702 var d = Date.now()-G.GOM.firstDisplayTime;
3703 if( d < G.O.galleryRenderDelay ) {
3704 // display after defined delay
3705 // setTimeout( function() { GalleryRenderPart1( albumIdx )}, G.O.galleryRenderDelay-d );
3706 requestTimeout( function() { GalleryRenderPart1( albumIdx )}, G.O.galleryRenderDelay-d );
3707 }
3708 else {
3709 GalleryRenderPart1( albumIdx );
3710 }
3711 G.O.galleryRenderDelay = 0;
3712
3713 }
3714 else {
3715 var hideNavigationBar = false;
3716 if( G.GOM.navigationBar.$newContent.children().length == 0 ) {
3717 hideNavigationBar = true;
3718 }
3719
3720 // hide everything
3721 var tweenable = new NGTweenable();
3722 tweenable.tween({
3723 from: { 'opacity': 1 },
3724 to: { 'opacity': 0 },
3725 duration: 300,
3726 easing: 'easeInQuart',
3727 attachment: { h: hideNavigationBar },
3728 step: function (state, att) {
3729 G.$E.conTnParent.css({'opacity': state.opacity });
3730 if( att.h ) {
3731 G.$E.conNavigationBar.css({ 'opacity': state.opacity });
3732 }
3733 },
3734 finish: function (state, att) {
3735 if( att.h ) {
3736 G.$E.conNavigationBar.css({ 'opacity': 0, 'display': 'none' });
3737 }
3738 // scroll to top of the gallery if needed
3739
3740 G.GOM.ScrollToTop();
3741
3742 GalleryRenderPart1( albumIdx );
3743 }
3744 });
3745 }
3746 }
3747
3748
3749 function GalleryRenderPart1( albumIdx ) {
3750 // display new navigation bar
3751 var oldN = G.$E.conNavigationBar.children().length;
3752 G.$E.conNavigationBar.empty();
3753 G.GOM.navigationBar.$newContent.children().clone(true,true).appendTo(G.$E.conNavigationBar);
3754 // G.GOM.navigationBar.$newContent.appendTo(G.$E.conNavigationBar);
3755 if( G.$E.conNavigationBar.children().length > 0 && oldN == 0 ) {
3756 G.$E.conNavigationBar.css({ 'opacity': 0, 'display': 'block' });
3757 var tweenable = new NGTweenable();
3758 tweenable.tween({
3759 from: { opacity: 0 },
3760 to: { opacity: 1 },
3761 duration: 200,
3762 easing: 'easeInQuart',
3763 step: function (state) {
3764 // window.ng_draf( function() {
3765 G.$E.conNavigationBar.css( state );
3766 // });
3767 },
3768 finish: function (state) {
3769 // window.ng_draf( function() {
3770 G.$E.conNavigationBar.css({ 'opacity': 1 });
3771 // display gallery
3772 // GalleryRenderPart2( albumIdx );
3773 // setTimeout(function(){ GalleryRenderPart2(albumIdx) }, 60);
3774 requestTimeout(function(){ GalleryRenderPart2(albumIdx) }, 20);
3775 // });
3776 }
3777 });
3778 }
3779 else {
3780 requestTimeout(function(){ GalleryRenderPart2(albumIdx) }, 20);
3781 }
3782
3783 }
3784
3785 // Gallery render part 2 -> remove all existing thumbnails
3786 function GalleryRenderPart2(albumIdx) {
3787
3788 G.GOM.lastZIndex = parseInt(G.$E.base.css('z-index'));
3789 if( isNaN(G.GOM.lastZIndex) ) {
3790 G.GOM.lastZIndex=0;
3791 }
3792 G.$E.conTnParent.css({ 'opacity': 0 });
3793 G.$E.conTn.off().empty();
3794 var l = G.I.length;
3795 for( var i = 0; i < l ; i++ ) {
3796 // reset each item
3797 var item = G.I[i];
3798 item.hovered = false;
3799 item.$elt = null;
3800 item.$Elts = [];
3801 item.eltTransform = [];
3802 item.eltFilter = [];
3803 item.width = 0;
3804 item.height = 0;
3805 item.left = 0;
3806 item.top = 0;
3807 item.resizedContentWidth = 0;
3808 item.resizedContentHeight = 0;
3809 item.thumbnailImgRevealed = false;
3810 }
3811
3812 if( G.CSStransformName == null ) {
3813 G.$E.conTn.css('left', '0px');
3814 }
3815 else {
3816 // G.$E.conTn.css( G.CSStransformName, 'translateX(0px)');
3817 G.$E.conTn.css( G.CSStransformName, 'none');
3818 }
3819
3820 // setTimeout(function(){ GalleryRenderPart3(albumIdx) }, 60);
3821 requestTimeout(function(){ GalleryRenderPart3(albumIdx) }, 20);
3822 // GalleryRenderPart3(albumIdx);
3823
3824 }
3825
3826 // Gallery render part 3 -> start building the new gallery
3827 function GalleryRenderPart3(albumIdx) {
3828 var d = new Date();
3829
3830 G.$E.conTnParent.css( 'opacity', 1);
3831
3832 G.GOM.items = [];
3833 G.GOM.displayedMoreSteps = 0;
3834 // retrieve label height
3835 if( G.O.thumbnailLabel.get('position') == 'onBottom' ) {
3836 // retrieve height each time because size can change depending on thumbnail's settings
3837 G.tn.labelHeight[G.GOM.curNavLevel] = ThumbnailGetLabelHeight();
3838 }
3839 else {
3840 G.tn.labelHeight[G.GOM.curNavLevel] = 0;
3841 }
3842 G.GOM.albumIdx=albumIdx;
3843
3844 TriggerCustomEvent('galleryRenderEnd');
3845 var fu=G.O.fnGalleryRenderEnd;
3846 if( fu !== null ) {
3847 // typeof fu == 'function' ? fu(albumIdx) : window[fu](albumIdx);
3848 typeof fu == 'function' ? fu(G.I[G.GOM.albumIdx] ) : window[fu](G.I[G.GOM.albumIdx] );
3849 }
3850
3851 // Step 1: populate GOM
3852 if( GalleryPopulateGOM() ) {
3853 // step 2: calculate layout
3854 GallerySetLayout();
3855
3856 // step 3: display whole gallery
3857 GalleryAppear();
3858
3859 // step 4: display thumbnails
3860 GalleryDisplayPart1();
3861 requestTimeout(function(){ GalleryDisplayPart2( false ) }, 20);
3862 }
3863 else {
3864 //
3865 G.galleryResizeEventEnabled = true;
3866 }
3867
3868 if( G.O.debugMode ) { console.log('GalleryRenderPart3: '+ (new Date()-d)); }
3869
3870 }
3871
3872
3873 // Resize the gallery
3874 function GalleryResize() {
3875 var d = new Date();
3876 G.galleryResizeEventEnabled = false;
3877 // G.GOM.cache.areaWidth=G.$E.conTnParent.width();
3878 if( GallerySetLayout() == false ) {
3879 G.galleryResizeEventEnabled = true;
3880 if( G.O.debugMode ) { console.log('GalleryResize1: '+ (new Date()-d)); }
3881 return;
3882 }
3883 if( G.O.debugMode ) { console.log('GalleryResizeSetLayout: '+ (new Date()-d)); }
3884
3885 GalleryDisplayPart1();
3886 GalleryDisplayPart2( false );
3887
3888 if( G.O.debugMode ) { console.log('GalleryResizeFull: '+ (new Date()-d)); }
3889 }
3890
3891
3892
3893 // copy items (album content) to GOM
3894 // returns:
3895 // true: thumbnail image size is needed for the layout, but not set -> retrieve the sizes and display gallery
3896 function GalleryPopulateGOM() {
3897
3898 var preloadImages = '';
3899 var albumID = G.I[G.GOM.albumIdx].GetID();
3900 var l = G.I.length;
3901 var cnt = 0;
3902
3903 for( var idx = 0; idx < l; idx++ ) {
3904 var item = G.I[idx];
3905 // check album
3906 if( item.isToDisplay(albumID) ) {
3907 var w = item.thumbImg().width;
3908 var h = item.thumbImg().height;
3909 // if unknown image size and layout is not grid --> we need to retrieve the size of the images
3910 if( G.layout.prerequisite.imageSize && ( w == 0 || h == 0) ) {
3911 preloadImages += '<img src="'+item.thumbImg().src+'" data-idx="'+cnt+'" data-albumidx="'+G.GOM.albumIdx+'">';
3912 }
3913
3914 // set default size if required
3915 if( h == 0 ) {
3916 h = G.tn.defaultSize.getHeight();
3917 }
3918 if( w == 0 ) {
3919 w = G.tn.defaultSize.getWidth();
3920 }
3921 var tn = new G.GOM.GTn(idx, w, h);
3922 G.GOM.items.push(tn);
3923 cnt++;
3924 }
3925 }
3926
3927 TriggerCustomEvent('galleryObjectModelBuilt');
3928 var fu = G.O.fnGalleryObjectModelBuilt;
3929 if( fu !== null ) {
3930 typeof fu == 'function' ? fu() : window[fu]();
3931 }
3932
3933 if( preloadImages != '' ) {
3934 // preload images to retrieve their size and then resize the gallery (=GallerySetLayout()+ GalleryDisplay())
3935 var $newImg = jQuery(preloadImages);
3936 var gi_imgLoad = ngimagesLoaded( $newImg );
3937 $newImg = null;
3938 gi_imgLoad.on( 'progress', function( instance, image ) {
3939
3940 if( image.isLoaded ) {
3941 var idx = image.img.getAttribute('data-idx');
3942 var albumIdx = image.img.getAttribute('data-albumidx');
3943 if( albumIdx == G.GOM.albumIdx ) {
3944 // ignore event if not on current album
3945 var curTn = G.GOM.items[idx];
3946 curTn.imageWidth = image.img.naturalWidth;
3947 curTn.imageHeight = image.img.naturalHeight;
3948 var item = G.I[curTn.thumbnailIdx];
3949 item.thumbs.width[G.GOM.curNavLevel][G.GOM.curWidth] = curTn.imageWidth;
3950 item.thumbs.height[G.GOM.curNavLevel][G.GOM.curWidth] = curTn.imageHeight;
3951
3952 // resize the gallery
3953 G.GalleryResizeThrottled();
3954
3955 // set the retrieved size to all levels with same configuration
3956 var object = item.thumbs.width.l1;
3957 for (let property in object) {
3958 if (object.hasOwnProperty(property)) {
3959 if( property != G.GOM.curWidth ) {
3960 if( G.tn.settings.width.l1[property] == G.tn.settings.getW() && G.tn.settings.height.l1[property] == G.tn.settings.getH() ) {
3961 item.thumbs.width.l1[property] = curTn.imageWidth;
3962 item.thumbs.height.l1[property] = curTn.imageHeight;
3963 }
3964 }
3965 }
3966 }
3967 object = item.thumbs.width.lN;
3968 for (let property in object) {
3969 if (object.hasOwnProperty(property)) {
3970 if( property != G.GOM.curWidth ) {
3971 if( G.tn.settings.width.lN[property] == G.tn.settings.getW() && G.tn.settings.height.lN[property] == G.tn.settings.getH() ) {
3972 item.thumbs.width.lN[property] = curTn.imageWidth;
3973 item.thumbs.height.lN[property] = curTn.imageHeight;
3974 }
3975 }
3976 }
3977 }
3978 }
3979 }
3980 });
3981 G.galleryResizeEventEnabled = true;
3982 return false;
3983 }
3984 else {
3985 return true;
3986 }
3987
3988 }
3989
3990 //----- Calculate the layout of the thumbnails for the full gallery
3991 function GallerySetLayout() {
3992 var r = true;
3993 // width of the available area
3994 G.GOM.cache.areaWidth = G.$E.conTnParent.width();
3995 G.GOM.displayArea = { width:0, height:0 };
3996
3997 switch( G.layout.engine ) {
3998 case 'JUSTIFIED':
3999 r = GallerySetLayoutWidthtAuto();
4000 break;
4001 case 'CASCADING':
4002 r = GallerySetLayoutHeightAuto();
4003 break;
4004 case 'MOSAIC':
4005 r = GallerySetLayoutMosaic();
4006 break;
4007 case 'GRID':
4008 default:
4009 r = GallerySetLayoutGrid();
4010 break;
4011 }
4012
4013 TriggerCustomEvent('galleryLayoutApplied');
4014 var fu = G.O.fnGalleryLayoutApplied;
4015 if( fu !== null ) {
4016 typeof fu == 'function' ? fu() : window[fu]();
4017 }
4018 return r;
4019
4020 }
4021
4022
4023 //----- CASCADING LAYOUT
4024 function GallerySetLayoutHeightAuto() {
4025 var curCol = 0,
4026 areaWidth = G.GOM.cache.areaWidth,
4027 curRow = 0,
4028 colHeight = [],
4029 maxCol = NbThumbnailsPerRow(areaWidth),
4030 gutterWidth = 0,
4031 gutterHeight = G.tn.settings.GetResponsive('gutterHeight');
4032 var w = 0;
4033 var scaleFactor = 1;
4034 var tnWidth = G.tn.defaultSize.getOuterWidth();
4035 var nbTn = G.GOM.items.length;
4036 var curPosY = 0;
4037
4038 if( G.O.thumbnailAlignment == 'justified' ) {
4039 maxCol = Math.min(maxCol, nbTn);
4040 gutterWidth = ( maxCol == 1 ? 0 : (areaWidth - (maxCol * tnWidth) ) / (maxCol - 1) );
4041 }
4042 else {
4043 gutterWidth = G.tn.settings.GetResponsive('gutterWidth');
4044 }
4045
4046
4047 var borderWidth = G.tn.opt.Get('borderHorizontal') * 2;
4048 var borderHeight = G.tn.opt.Get('borderVertical') * 2;
4049
4050 G.GOM.lastFullRow=-1; // feature disabled
4051
4052 // Retrieve the real used width of the area (the evaluation is based on the content of the first line)
4053 if( G.O.thumbnailAlignment == 'fillWidth' ) {
4054 // fillWidth --> evaluate scale factor and number of columns
4055 var totalGutterWidth = (maxCol - 1) * gutterWidth;
4056 scaleFactor = (areaWidth - totalGutterWidth) / (maxCol * tnWidth);
4057 if( scaleFactor > 1 ) {
4058 maxCol++; // add one column and re-evaluate the scale factor
4059 }
4060 totalGutterWidth = (maxCol - 1) * gutterWidth;
4061 scaleFactor = Math.min( (areaWidth - totalGutterWidth) / (maxCol*tnWidth), 1); // no upscale
4062 }
4063
4064
4065 tnWidth = Math.round( tnWidth * scaleFactor);
4066 var contentWidth = tnWidth - borderWidth;
4067
4068 // loop to position the thumbnails, and set their size
4069 var baseHeight = Math.round( G.tn.opt.Get('baseGridHeight') * scaleFactor );
4070 for( var i = 0; i < nbTn ; i++ ) {
4071 var curTn = G.GOM.items[i];
4072 if( curTn.deleted == true ) { break; } // item is logically deleted
4073 if( curTn.imageHeight > 0 && curTn.imageWidth > 0 ) {
4074 var curPosX = 0,
4075 curPosY = 0;
4076 var imageRatio = curTn.imageHeight / curTn.imageWidth;
4077 // curTn.resizedContentWidth = tnWidth - borderWidth;
4078 curTn.resizedContentWidth = contentWidth;
4079 curTn.resizedContentHeight = curTn.resizedContentWidth * imageRatio;
4080 if( baseHeight > 0 ) {
4081 // grid based vertical position
4082 var t = Math.max( Math.trunc(curTn.resizedContentHeight/baseHeight), 1) ;
4083 curTn.resizedContentHeight = baseHeight * t + ((t-1)*(borderHeight+gutterHeight));
4084 }
4085
4086 curTn.height = curTn.resizedContentHeight + borderHeight + G.tn.labelHeight.get();
4087 curTn.width = tnWidth;
4088 curTn.row = 0;
4089
4090 if( curRow == 0 ) {
4091 // first row
4092 curPosX = curCol * (tnWidth + gutterWidth);
4093 colHeight[curCol] = curTn.height + gutterHeight;
4094
4095 curCol++;
4096 if( curCol >= maxCol ) {
4097 curCol = 0;
4098 curRow++;
4099 }
4100 }
4101 else {
4102 var c=0,
4103 minColHeight=colHeight[0];
4104 for( var j = 1; j < maxCol; j++) {
4105 if( (colHeight[j] + 5) < minColHeight ) { // +5 --> threshold
4106 minColHeight = colHeight[j];
4107 c = j;
4108 //break;
4109 }
4110 }
4111 curPosY = colHeight[c];
4112 curPosX = c * (tnWidth + gutterWidth);
4113 colHeight[c] = curPosY + curTn.height + gutterHeight;
4114 }
4115
4116 var x = curPosX;
4117 if( G.O.RTL) {
4118 x= w - curPosX - tnWidth;
4119 }
4120
4121 curTn.left = x;
4122 curTn.top = curPosY;
4123 }
4124 }
4125
4126 G.GOM.displayArea.width= maxCol * (tnWidth + gutterWidth) - gutterWidth;
4127 return true;
4128 }
4129
4130
4131 //----- JUSTIFIED LAYOUT
4132 function GallerySetLayoutWidthtAuto() {
4133 var curWidth = 0,
4134 areaWidth = G.GOM.cache.areaWidth,
4135 lastPosX = 0,
4136 curPosY = 0,
4137 rowLastItem = [],
4138 rowNum = 0,
4139 rowHeight = [],
4140 bNewRow = false,
4141 cnt = 0,
4142 gutterWidth = G.tn.settings.GetResponsive('gutterWidth'),
4143 gutterHeight = G.tn.settings.GetResponsive('gutterHeight');
4144 // by grief-of-these-days
4145 var maxRowHeightVertical = 0; // max height of a row with vertical thumbs
4146 var maxRowHeightHorizontal = 0; // max height of a row with horizontal thumbs
4147 var rowHasVertical = false; // current row has vertical thumbs
4148 var rowHasHorizontal = false; // current row has horizontal thumbs
4149
4150 var tnHeight = G.tn.defaultSize.getOuterHeight();
4151 var borderWidth = G.tn.opt.Get('borderHorizontal') * 2;
4152 var borderHeight = G.tn.opt.Get('borderVertical') * 2;
4153 var nbTnInCurrRow = 1;
4154 var nbTn = G.GOM.items.length;
4155
4156 // first loop --> retrieve each row image height
4157 for( var i = 0; i < nbTn ; i++ ) {
4158 let curTn = G.GOM.items[i];
4159 if( curTn.deleted == true ) { break; } // item is logically deleted
4160 if( curTn.imageWidth > 0 ) {
4161 let imageRatio = curTn.imageWidth / curTn.imageHeight;
4162 let imageWidth = Math.floor( tnHeight * imageRatio );
4163
4164 if( bNewRow ) {
4165 bNewRow = false;
4166 rowNum++;
4167 curWidth = 0;
4168 rowHasVertical = false;
4169 rowHasHorizontal = false;
4170 nbTnInCurrRow = 1;
4171 }
4172 // by grief-of-these-days
4173 if( curTn.imageHeight > curTn.imageWidth ) {
4174 rowHasVertical = true;
4175 }
4176 else {
4177 rowHasHorizontal = true;
4178 }
4179
4180 if( (curWidth + gutterWidth + imageWidth) < (areaWidth - (nbTnInCurrRow * borderWidth)) ) {
4181 // enough place left in the current row
4182 curWidth += imageWidth + gutterWidth;
4183 rowHeight[rowNum] = tnHeight;
4184
4185 // prevent incomplete row from being heigher than the previous ones.
4186 // by grief-of-these-days
4187 var rowHeightLimit = Math.max(rowHasVertical ? maxRowHeightVertical : 0, rowHasHorizontal ? maxRowHeightHorizontal : 0);
4188 if( rowHeightLimit > 0 ) {
4189 rowHeight[rowNum] = Math.min(rowHeight[rowNum], rowHeightLimit);
4190 }
4191
4192 rowLastItem[rowNum] = i;
4193 }
4194 else {
4195 // new row after current item --> we need to adujet the row height to have enough space for the current thumbnail
4196 curWidth += gutterWidth+imageWidth;
4197 let ratio = (areaWidth - nbTnInCurrRow * borderWidth) / curWidth;
4198 let rH = Math.floor(tnHeight * ratio);
4199 rowHeight[rowNum] = rH;
4200
4201 // save the max row height for each thumb orientation.
4202 // by grief-of-these-days
4203 if( rowHasVertical ) {
4204 maxRowHeightVertical = Math.max( maxRowHeightVertical, rH );
4205 }
4206 if( rowHasHorizontal ) {
4207 maxRowHeightHorizontal = Math.max( maxRowHeightHorizontal, rH );
4208 }
4209
4210 rowLastItem[rowNum] = i;
4211 bNewRow = true;
4212 }
4213 cnt++;
4214 nbTnInCurrRow++;
4215 }
4216 }
4217
4218 rowNum = 0;
4219 curPosY = 0;
4220 lastPosX = 0;
4221 cnt = 0;
4222
4223 G.GOM.lastFullRow = 0; // display at leat 1 row (even if not full)
4224
4225 // second loop --> calculate each thumbnail size
4226 for( var i = 0; i < nbTn ; i++ ) {
4227 let curTn = G.GOM.items[i];
4228 if( curTn.imageWidth > 0 ) {
4229 let imageRatio = curTn.imageWidth / curTn.imageHeight;
4230 let imageWidth = Math.floor( imageRatio * rowHeight[rowNum] ); // border is already NOT included
4231
4232 if( i == rowLastItem[rowNum] ) {
4233 // row last item --> adjust image width because of rounding problems
4234 if( rowLastItem.length != (rowNum+1) ) {
4235 // last item in current row -> use the full remaining width
4236 imageWidth = areaWidth - lastPosX - borderWidth;
4237 }
4238 else {
4239 // very last item (on the last row)
4240 if( (lastPosX + gutterWidth + imageWidth + borderWidth ) > areaWidth ) {
4241 // reduce size if image is wider as the remaining space
4242 imageWidth = areaWidth - lastPosX - borderWidth;
4243 }
4244 }
4245 }
4246
4247 let rh = parseInt( rowHeight[rowNum] );
4248 imageWidth = parseInt( imageWidth );
4249
4250 // thumbnail image size
4251 curTn.resizedContentWidth = imageWidth;
4252 curTn.resizedContentHeight = rh;
4253 // thumbnail position and size
4254 curTn.width = imageWidth + borderWidth;
4255 curTn.height= rh + G.tn.labelHeight.get() + borderHeight;
4256 curTn.row = rowNum;
4257
4258 curTn.top = curPosY;
4259 let x = lastPosX;
4260 if( G.O.RTL) {
4261 x = areaWidth - lastPosX - curTn.width ;
4262 }
4263 curTn.left = x;
4264
4265 lastPosX += curTn.width + gutterWidth;
4266
4267 if( i == rowLastItem[rowNum] ) {
4268 // start a new row
4269 curPosY += curTn.height + gutterHeight;
4270 G.GOM.lastFullRow = rowNum - 1;
4271 rowNum++;
4272 lastPosX = 0;
4273 }
4274 cnt++;
4275 }
4276 else {
4277 return false;
4278 }
4279 }
4280
4281 // hover effect on gallery (vs on thumbnail) --> experimental / not used
4282 if( false ) {
4283 var newTop = 0;
4284 if( typeof GOMidx !== 'undefined' ) {
4285 if( G.GOM.albumIdx != -1 ) {
4286 var hoveredTn = G.GOM.items[GOMidx];
4287
4288 // hovered thumbnail
4289 hoveredTn.width += 40;
4290 hoveredTn.height += 40;
4291
4292 for( var i = 0; i < nbTn ; i++ ) {
4293 var curTn = G.GOM.items[i];
4294 if( curTn.imageWidth > 0 ) {
4295 if( curTn.row == hoveredTn.row ) {
4296 // hovered row
4297 newTop = 40;
4298 if( hoveredTn.thumbnailIdx != curTn.thumbnailIdx ) {
4299 // not hovered thumbnail
4300 curTn.top += 30;
4301 curTn.width -= 20;
4302 curTn.height -= 20;
4303 }
4304 }
4305 else {
4306 // not hovered row
4307 if( curTn.row == 0 ) {
4308 // first row
4309 }
4310 else {
4311 curTn.top += newTop;
4312 }
4313 }
4314 }
4315 }
4316 }
4317 }
4318 }
4319
4320 G.GOM.displayArea.width = areaWidth;
4321 return true;
4322 }
4323
4324
4325 //----- MOSAIC LAYOUT
4326 // Grid using a user defined pattern layout
4327 // With this layout, a pattern definition is handeld a row
4328 function GallerySetLayoutMosaic() {
4329 var areaWidth = G.GOM.cache.areaWidth;
4330 var gutterHeight = G.tn.settings.GetResponsive('gutterHeight');
4331 var gutterWidth = G.tn.settings.GetResponsive('gutterWidth');
4332 var borderWidth = G.tn.opt.Get('borderHorizontal') * 2;
4333 var borderHeight = G.tn.opt.Get('borderVertical') * 2;
4334
4335 var nbTn = G.GOM.items.length;
4336 var row = 0;
4337 var h = 0;
4338 var n = 0;
4339
4340
4341 // first loop: evaluate the gallery width based on the first row
4342 var nbCols = 0;
4343 var maxW = 0;
4344 let mosaicPattern = G.tn.settings.getMosaic();
4345 for( var i = 0; i < nbTn ; i++ ) {
4346 let curPatternElt = mosaicPattern[n];
4347
4348 var cLeft = (curPatternElt.c - 1) * G.tn.defaultSize.getOuterWidth() + (curPatternElt.c - 1) * gutterWidth;
4349 var cWidth = curPatternElt.w * G.tn.defaultSize.getOuterWidth() + (curPatternElt.w - 1) * gutterWidth;
4350
4351 maxW = Math.max(maxW, cLeft + cWidth );
4352
4353 nbCols = Math.max(nbCols, (curPatternElt.c - 1) + curPatternElt.w );
4354
4355 n++;
4356 if( n >= mosaicPattern.length ) {
4357 // end of pattern
4358 break;
4359 }
4360 }
4361 var totalGutterWidth = (nbCols - 1) * gutterWidth;
4362 var scaleFactor = Math.min( (areaWidth - totalGutterWidth ) / ( maxW - totalGutterWidth ), 1);
4363
4364 // second loop: position all the thumbnails based on the layout pattern
4365 row = 0;
4366 n = 0;
4367 // let mosaicPattern = G.tn.settings.getMosaic();
4368 for( var i = 0; i < nbTn ; i++ ) {
4369 let curTn = G.GOM.items[i];
4370 let curPatternElt = mosaicPattern[n];
4371
4372 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)) ;
4373 if( row > 0 ) {
4374 curTn.top += gutterHeight;
4375 }
4376
4377 curTn.left = (curPatternElt.c - 1) * Math.round(G.tn.defaultSize.getOuterWidth()*scaleFactor) + (curPatternElt.c - 1) * gutterWidth;
4378
4379 curTn.height = Math.round(curPatternElt.h * G.tn.defaultSize.getOuterHeight() * scaleFactor) + (curPatternElt.h - 1) * gutterHeight + (G.tn.labelHeight.get() * curPatternElt.h);
4380 curTn.resizedContentHeight = curTn.height - G.tn.labelHeight.get() - borderHeight;
4381
4382 curTn.width = Math.round(curPatternElt.w * G.tn.defaultSize.getOuterWidth()*scaleFactor) + (curPatternElt.w - 1) * gutterWidth;
4383 curTn.resizedContentWidth = curTn.width - borderWidth ;
4384
4385 curTn.row = row;
4386 if( row == 0 ) {
4387 h=Math.max(h, curTn.top + curTn.height);
4388 }
4389
4390 n++;
4391 if( n >= mosaicPattern.length ) {
4392 // end pattern -> new line
4393 n = 0;
4394 row++;
4395 }
4396 }
4397
4398 G.GOM.displayArea.width = (maxW - totalGutterWidth) * scaleFactor + totalGutterWidth;
4399 return true;
4400 }
4401
4402
4403
4404 // --- GRID LAYOUT
4405 function GallerySetLayoutGrid() {
4406 var curPosX= 0,
4407 curPosY= 0,
4408 areaWidth= G.GOM.cache.areaWidth,
4409 gutterWidth= 0,
4410 gutterHeight= G.tn.settings.GetResponsive('gutterHeight'),
4411 maxCol= NbThumbnailsPerRow(areaWidth),
4412 w= 0,
4413 cols= [],
4414 curCol= 0,
4415 newAreaWidth = areaWidth,
4416 tnWidth= G.tn.defaultSize.getOuterWidth();
4417 var scaleFactor = 1;
4418 var nbTn= G.GOM.items.length;
4419 var borderWidth = G.tn.opt.Get('borderHorizontal') * 2;
4420 var borderHeight = G.tn.opt.Get('borderVertical') * 2;
4421
4422 // retrieve gutter width
4423 if( G.O.thumbnailAlignment == 'justified' ) {
4424 maxCol = Math.min( maxCol, nbTn);
4425 gutterWidth = (maxCol==1 ? 0 : (areaWidth-(maxCol*tnWidth))/(maxCol-1));
4426 }
4427 else {
4428 gutterWidth = G.tn.settings.GetResponsive('gutterWidth');
4429 }
4430
4431 // first loop to retrieve the real used width of the area (the evaluation is based on the content of the first line)
4432 // Retrieve the real used width of the area (the evaluation is based on the content of the first line)
4433 if( G.O.RTL || G.O.thumbnailAlignment == 'fillWidth' ) {
4434 // scaled --> evaluate scale factor and number of columns
4435 var totalGutterWidth = (maxCol-1) * gutterWidth;
4436 scaleFactor = (areaWidth - totalGutterWidth) / (maxCol*tnWidth);
4437 if( scaleFactor > 1 ) {
4438 maxCol++; // add one column and re-evaluate the scale factor
4439 }
4440 totalGutterWidth = (maxCol-1) * gutterWidth;
4441 scaleFactor = Math.min( (areaWidth - totalGutterWidth) / (maxCol*tnWidth), 1); // no upscale
4442 newAreaWidth = (maxCol*tnWidth) + totalGutterWidth;
4443 }
4444
4445
4446 G.GOM.lastFullRow = 0 ; // display at leat 1 row (even if not full)
4447 // var lastPosY = 0;
4448 var row = 0;
4449
4450 tnWidth = Math.round(tnWidth * scaleFactor);
4451 var contentWidth = tnWidth - borderWidth;
4452 var tnHeight = Math.round(G.tn.defaultSize.getOuterHeight() * scaleFactor) + G.tn.labelHeight.get();
4453 var contentHeight = Math.round( G.tn.defaultSize.getOuterHeight() * scaleFactor) - borderHeight;
4454
4455 // loop to position and to set size of all thumbnails
4456 for( var i = 0; i < nbTn ; i++ ) {
4457 if( curPosY == 0 ) {
4458 curPosX = curCol * (tnWidth + gutterWidth)
4459 cols[curCol] = curPosX;
4460 w = curPosX + tnWidth;
4461 }
4462 else {
4463 curPosX = cols[curCol];
4464 }
4465
4466 var x = curPosX;
4467 if( G.O.RTL ) {
4468 x = parseInt(newAreaWidth) - curPosX - tnWidth;
4469 }
4470
4471 // MANDATORY : set thumbnail position AND size
4472 var curTn=G.GOM.items[i];
4473 curTn.top = curPosY;
4474 curTn.left = x;
4475 curTn.height = tnHeight;
4476 curTn.width = tnWidth;
4477 // image size
4478 if( G.O.thumbnailAlignment == 'fillWidth' ) {
4479 curTn.resizedContentWidth = contentWidth;
4480 curTn.resizedContentHeight = contentHeight;
4481 }
4482 curTn.row = row;
4483 // lastPosY = curPosY;
4484
4485 curCol++;
4486 if( curCol >= maxCol ){
4487 // new line
4488 curCol = 0;
4489 curPosY += tnHeight + gutterHeight;
4490 G.GOM.lastFullRow = row;
4491 row++;
4492 }
4493 }
4494 G.GOM.displayArea.width = w;
4495
4496 return true;
4497 }
4498
4499
4500
4501
4502 //----- Display the thumbnails according to the calculated layout
4503 function GalleryDisplayPart1() {
4504 if( G.CSStransformName == null ) {
4505 G.$E.conTn.css( 'left' , '0px');
4506 }
4507 else {
4508 G.$E.conTn.css( G.CSStransformName , 'none');
4509 }
4510 // CacheViewport();
4511 }
4512
4513 function CacheViewport() {
4514 G.GOM.cache.viewport = getViewport();
4515 // G.GOM.cache.areaWidth = G.$E.conTnParent.width();
4516 G.GOM.cache.areaWidth = G.$E.base.width();
4517
4518 // position of the gallery container
4519 // we use the position of the loadingbar because :
4520 // - the gallery may be wrong positioned due to one display animation currently running
4521 // - the loadingbar is never animated and positioned just before the gallery container
4522 //G.GOM.cache.containerOffset = G.$E.conTnParent.offset();
4523 if( !G.O.lightboxStandalone ) {
4524 G.GOM.cache.containerOffset = G.$E.conLoadingB.offset();
4525 }
4526 }
4527
4528
4529
4530 function GalleryDisplayPart2( forceTransition ) {
4531 CacheViewport();
4532
4533 var nbTn = G.GOM.items.length;
4534 G.GOM.itemsDisplayed = 0;
4535 var threshold = 50;
4536 var cnt = 0; // counter for delay between each thumbnail display
4537
4538
4539 GalleryRenderGetInterval();
4540
4541 for( var i = 0; i < nbTn ; i++ ) {
4542 let curTn = G.GOM.items[i];
4543 if( i >= G.GOM.displayInterval.from && cnt < G.GOM.displayInterval.len ) {
4544 curTn.inDisplayArea = true;
4545 if( forceTransition ) {
4546 curTn.neverDisplayed = true;
4547 }
4548 G.GOM.itemsDisplayed++;
4549 cnt++;
4550 }
4551 else{
4552 curTn.inDisplayArea = false;
4553 }
4554 }
4555
4556 // bottom of the gallery (pagination, more button...)
4557 GalleryBottomManage();
4558
4559 var tnToDisplay = [];
4560 var tnToReDisplay = [];
4561
4562 CacheViewport();
4563 G.GOM.clipArea.top = -1;
4564 cnt = 0 ;
4565 var lastTnIdx = -1;
4566 G.GOM.clipArea.height = 0;
4567 // NOTE: loop always the whole GOM.items --> in case an already displayed thumbnail needs to be removed
4568 for( var i = 0; i < nbTn ; i++ ) {
4569 let curTn = G.GOM.items[i];
4570 if( curTn.inDisplayArea ) {
4571 if( G.GOM.clipArea.top == -1 ) {
4572 G.GOM.clipArea.top = curTn.top;
4573 }
4574 if( (curTn.top - G.GOM.clipArea.top) <= -1 ) {
4575 // with mosaic layout, the first thumbnail may not give the top position
4576 G.GOM.clipArea.top = curTn.top;
4577 }
4578
4579 G.GOM.clipArea.height = Math.max( G.GOM.clipArea.height, curTn.top-G.GOM.clipArea.top + curTn.height);
4580
4581 if( curTn.neverDisplayed ) {
4582 // thumbnail is not displayed -> check if in viewport to display or not
4583 var top = G.GOM.cache.containerOffset.top + (curTn.top - G.GOM.clipArea.top);
4584 // var left=containerOffset.left+curTn.left;
4585 if( (top + curTn.height) >= (G.GOM.cache.viewport.t - threshold) && top <= (G.GOM.cache.viewport.t + G.GOM.cache.viewport.h + threshold) ) {
4586 // build thumbnail
4587 let item = G.I[curTn.thumbnailIdx];
4588 if( item.$elt == null ) {
4589 // ThumbnailBuild( item, curTn.thumbnailIdx, i, (i+1) == nbTn );
4590 ThumbnailBuild( item, curTn.thumbnailIdx, i );
4591 }
4592 tnToDisplay.push({idx:i, delay:cnt, top: curTn.top, left: curTn.left});
4593 cnt++;
4594 }
4595 }
4596 else {
4597 tnToReDisplay.push({idx: i, delay: 0, top: curTn.top, left: curTn.left});
4598 }
4599 // G.GOM.itemsDisplayed++;
4600 lastTnIdx = i;
4601 }
4602 else {
4603 curTn.displayed = false;
4604 let item = G.I[curTn.thumbnailIdx];
4605 if( item.$elt != null ){
4606 item.$elt.css({ opacity: 0, display: 'none' });
4607 }
4608 }
4609 }
4610
4611 var areaWidth = G.$E.conTnParent.width();
4612
4613 // set gallery area really used size
4614 // if( G.GOM.displayArea.width != G.GOM.displayAreaLast.width || G.GOM.displayArea.height != G.GOM.displayAreaLast.height ) {
4615 if( G.GOM.displayArea.width != G.GOM.displayAreaLast.width || G.GOM.clipArea.height != G.GOM.displayAreaLast.height ) {
4616 G.$E.conTn.width( G.GOM.displayArea.width ).height( G.GOM.clipArea.height );
4617 G.GOM.displayAreaLast.width = G.GOM.displayArea.width;
4618 G.GOM.displayAreaLast.height = G.GOM.clipArea.height;
4619 // G.GOM.displayAreaLast.height=G.GOM.displayArea.height-G.GOM.clipArea.top;
4620 }
4621
4622 if( areaWidth != G.$E.conTnParent.width() ) {
4623 // gallery area width changed since layout calculation (for example when a scrollbar appeared)
4624 // so we need re-calculate the layout before displaying the thumbnails
4625 G.GOM.cache.areaWidth = G.$E.conTnParent.width();
4626 GallerySetLayout();
4627 GalleryDisplayPart1();
4628 GalleryDisplayPart2( forceTransition );
4629 return;
4630 }
4631
4632 // counter of not displayed images (is displayed on the last thumbnail)
4633 if( G.layout.support.rows ) {
4634 if( G.galleryDisplayMode.Get() == 'ROWS' || (G.galleryDisplayMode.Get() == 'FULLCONTENT' && G.galleryLastRowFull.Get() && G.GOM.lastFullRow != -1) ){
4635 if( lastTnIdx < (nbTn - 1) ) {
4636 G.GOM.lastDisplayedIdxNew = lastTnIdx;
4637 }
4638 else {
4639 G.GOM.lastDisplayedIdxNew =- 1;
4640 }
4641 // remove last displayed counter
4642 if( G.GOM.lastDisplayedIdx != -1 ) {
4643 let item = G.I[G.GOM.items[G.GOM.lastDisplayedIdx].thumbnailIdx];
4644 item.$getElt('.nGY2GThumbnailIconsFullThumbnail').html('');
4645 }
4646 }
4647 }
4648
4649
4650 // batch set position (and display animation) to all thumbnails
4651 // first display newly built thumbnails
4652
4653 G.GOM.thumbnails2Display=[];
4654
4655 var duration = ThumbnailPreparePosition( tnToDisplay );
4656 ThumbnailPreparePosition( tnToReDisplay );
4657
4658 ThumbnailDisplayAnimBatch();
4659
4660 if( G.tn.opt.Get('displayTransition') == 'NONE' ) {
4661 G.galleryResizeEventEnabled = true;
4662 // GalleryThumbnailSliderBuildAndStart(); // image slider on last displayed thumbnail
4663 TriggerCustomEvent('galleryDisplayed');
4664 }
4665 else {
4666 // setTimeout(function() {
4667 requestTimeout( function() {
4668 // change value after the end of the display transistion of the newly built thumbnails
4669 G.galleryResizeEventEnabled = true;
4670 // GalleryThumbnailSliderBuildAndStart(); // image slider on last displayed thumbnail
4671 TriggerCustomEvent('galleryDisplayed');
4672 // }, nbBuild * G.tn.opt.Get('displayInterval'));
4673 }, duration * G.tn.opt.Get('displayInterval'));
4674 }
4675
4676 }
4677
4678
4679 function ThumbnailPreparePosition( lstThumb ) {
4680
4681 var nbBuild = lstThumb.length;
4682 if( nbBuild == 0 ) { return 0; }
4683
4684
4685 var displayOrder = G.tn.opt.Get('displayOrder');
4686
4687 if( displayOrder == 'random' ) {
4688 NGY2Tools.AreaShuffle( lstThumb );
4689 }
4690 else {
4691 if( displayOrder == 'rowByRow' && !( G.layout.engine == 'JUSTIFIED' || G.layout.engine == 'GRID' )) {
4692 displayOrder = '';
4693 }
4694 if( (displayOrder == 'colFromRight' || displayOrder == 'colFromLeft' ) && !(G.layout.engine == 'CASCADING' || G.layout.engine == 'GRID' )) {
4695 displayOrder = '';
4696 }
4697 }
4698
4699
4700 // DISPLAY COLUMN BY COLUMN
4701 if( displayOrder == 'colFromRight' || displayOrder == 'colFromLeft' ) {
4702 var tab = [];
4703 var cols = [];
4704 for( var i = 0; i < nbBuild ; i++ ) {
4705 if( tab[lstThumb[i].left] == undefined ) {
4706 tab[lstThumb[i].left] = [];
4707 cols.push( lstThumb[i].left );
4708 }
4709 tab[lstThumb[i].left].push( lstThumb[i].idx )
4710 }
4711 if( displayOrder == 'colFromRight' ) {
4712 cols = cols.reverse();
4713 }
4714 for( var i = 0; i < cols.length; i++ ) {
4715 var col = cols[i];
4716 for( var j = 0; j < tab[col].length; j++ ) {
4717 ThumbnailSetPosition( tab[col][j], i);
4718 }
4719 }
4720 return(i);
4721 }
4722
4723
4724 // STANDARD DISPLAY OR ROW BY ROW
4725 var d = 0;
4726 var top = lstThumb[0].top;
4727 for( var i = 0; i < nbBuild ; i++ ) {
4728 // ThumbnailSetPosition(tnToDisplay[i].idx, tnToDisplay[i].delay+10);
4729 // ThumbnailSetPosition(tnToDisplay[i].idx, i);
4730
4731 if( displayOrder == 'rowByRow' ) {
4732 // DISPLAY ROW BY ROW
4733 if( lstThumb[i].top > top ) {
4734 d++;
4735 top = lstThumb[i].top;
4736 }
4737 }
4738 else {
4739 d++;
4740 }
4741 ThumbnailSetPosition(lstThumb[i].idx, d);
4742 }
4743 return(d);
4744
4745 }
4746
4747 // Thumbnail: set the new position
4748 function ThumbnailSetPosition( GOMidx, cnt ) {
4749 var newTop= 0;
4750 var curTn= G.GOM.items[GOMidx];
4751 var idx= G.GOM.items[GOMidx].thumbnailIdx;
4752 var item= G.I[idx];
4753
4754 if( curTn.neverDisplayed ) {
4755 // thumbnail is built but has never been displayed (=first display)
4756 var top = curTn.top - G.GOM.clipArea.top;
4757 if( G.tn.opt.Get('stacks') > 0 ) {
4758 // we have stacks -> do not display them here. They will be displayed at the end of the display animation
4759 item.$elt.last().css({ display: 'block'});
4760 item.$elt.css({ top: top , left: curTn.left });
4761 }
4762 else {
4763 item.$elt.css({ display: 'block', top: top , left: curTn.left });
4764 }
4765 newTop=top;
4766
4767 // display the image of the thumbnail when fully loaded
4768 if( G.O.thumbnailWaitImageLoaded === true ) {
4769 var gi_imgLoad = ngimagesLoaded( item.$getElt('.nGY2TnImg2') );
4770 gi_imgLoad.on( 'progress', function( instance, image ) {
4771 if( image.isLoaded ) {
4772 var albumIdx = image.img.getAttribute('data-albumidx');
4773 if( albumIdx == G.GOM.albumIdx ) {
4774 // ignore event if not on current album
4775 var idx = image.img.getAttribute('data-idx');
4776 G.I[idx].ThumbnailImageReveal();
4777 }
4778 }
4779 });
4780 }
4781 // display the thumbnail
4782 ThumbnailAppear(GOMidx, cnt);
4783
4784 curTn.displayed = true;
4785 curTn.neverDisplayed = false;
4786 }
4787 else {
4788 var topOld = G.GOM.cache.containerOffset.top + item.top;
4789 var top = G.GOM.cache.containerOffset.top + (curTn.top - G.GOM.clipArea.top);
4790 newTop = curTn.top - G.GOM.clipArea.top;
4791 var vp = G.GOM.cache.viewport;
4792 if( G.O.thumbnailDisplayOutsideScreen || ( ( (topOld + curTn.height) >= (vp.t - vp.h) && topOld <= (vp.t + vp.h * 4) ) ||
4793 ( (top + curTn.height) >= (vp.t - vp.h) && top <= (vp.t + vp.h * 4) ) ) ) {
4794 // thumbnail positioned in enlarged viewport (viewport + 4 x viewport height) (v1.5: changed from 2 to 4)
4795 if( curTn.displayed ) {
4796 // thumbnail is displayed
4797 if( item.top != curTn.top || item.left != curTn.left ) {
4798 // set position
4799 if( G.O.galleryResizeAnimation == true ) {
4800 // with transition
4801 var tweenable = new NGTweenable();
4802 tweenable.tween({
4803 from: { top: item.top, left: item.left, height: item.height, width: item.width },
4804 to: { top: newTop, left: curTn.left, height: curTn.height, width: curTn.width },
4805 attachment: { $e: item.$elt },
4806 duration: 100,
4807 delay: cnt * G.tn.opt.Get('displayInterval') / 5,
4808 // easing: 'easeInOutQuad',
4809 easing: 'easeOutQuart',
4810 step: function (state, att) {
4811 // window.ng_draf( function() {
4812 att.$e.css(state);
4813 // });
4814 },
4815 finish: function (state, att) {
4816 var _this=this;
4817 // window.ng_draf( function() {
4818 _this.dispose();
4819 // });
4820 }
4821 });
4822 }
4823 else {
4824 // set position without transition
4825 // item.$elt.css({ top: curTn.top , left: curTn.left });
4826 item.$elt.css({ top: newTop , left: curTn.left });
4827 }
4828 }
4829 }
4830 else {
4831 // re-display thumbnail
4832 curTn.displayed = true;
4833 // item.$elt.css({ display: 'block', top: curTn.top , left: curTn.left, opacity:1 });
4834 item.$elt.css({ display: 'block', top: newTop, left: curTn.left, opacity: 1 });
4835 ThumbnailAppearFinish(item);
4836 }
4837 }
4838 else {
4839 // undisplay thumbnail if not in viewport+margin --> performance gain
4840 curTn.displayed = false;
4841 item.$elt.css({ display: 'none'});
4842 }
4843 }
4844 item.left = curTn.left;
4845 item.top = newTop;
4846
4847 // set new size if changed
4848 if( item.width != curTn.width || item.height != curTn.height ) {
4849 item.$elt.css({ width: curTn.width , height: curTn.height });
4850 item.width = curTn.width;
4851 item.height = curTn.height;
4852
4853 // if( curTn.resizedContentWidth > 0 ) {
4854 // resize also the content (=image)
4855 if( item.resizedContentWidth != curTn.resizedContentWidth || item.resizedContentHeight != curTn.resizedContentHeight ) {
4856 if( item.kind == 'albumUp' ) {
4857 // item.$getElt('.nGY2GThumbnailAlbumUp').css({'height': curTn.resizedContentHeight, 'width': curTn.resizedContentWidth});
4858 }
4859 else {
4860 item.$getElt('.nGY2GThumbnailImage').css({'height': curTn.resizedContentHeight, 'width': curTn.resizedContentWidth});
4861
4862 if( G.layout.engine == 'JUSTIFIED' ) {
4863 item.$getElt('.nGY2GThumbnailImg').css({'height': curTn.resizedContentHeight, 'width': curTn.resizedContentWidth});
4864 }
4865 }
4866 item.resizedContentWidth = curTn.resizedContentWidth;
4867 item.resizedContentHeight = curTn.resizedContentHeight;
4868 }
4869 }
4870
4871
4872 // add counter of remaining (not displayed) images
4873 if( G.GOM.lastDisplayedIdxNew == GOMidx && G.layout.support.rows ) {
4874 if( (G.galleryDisplayMode.Get() == 'ROWS' && G.galleryMaxRows.Get() > 0) || (G.galleryDisplayMode.Get() == 'FULLCONTENT' && G.galleryLastRowFull.Get() && G.GOM.lastFullRow != -1) ){
4875 // number of items
4876 var nb = G.GOM.items.length - GOMidx - 1;
4877 if( item.albumID != '0' && G.O.thumbnailLevelUp ) {
4878 nb--;
4879 }
4880
4881 if( nb > 0 ) {
4882 // display counter
4883 if( G.O.thumbnailOpenInLightox || G.O.thumbnailSliderDelay > 0 ) {
4884 item.$getElt('.nGY2GThumbnailIconsFullThumbnail').html( '+' + nb);
4885 }
4886
4887 // if( G.layout.engine == 'GRID' && G.GOM.slider.hostItem != G.GOM.NGY2Item(GOMidx) ) {
4888 // image slider on last displayed thumbnail
4889 if( G.O.thumbnailLabel.get('position') != 'right' && G.O.thumbnailLabel.get('position') != 'left' ) {
4890 if( G.GOM.slider.hostItem != G.GOM.NGY2Item(GOMidx) ) {
4891
4892 // set current slider back to initial content
4893 GalleryThumbnailSliderSetContent( G.GOM.slider.hostItem );
4894 // new slider
4895 G.GOM.slider.hostIdx = GOMidx;
4896 G.GOM.slider.hostItem = G.GOM.NGY2Item(GOMidx);
4897 G.GOM.slider.nextIdx = GOMidx;
4898 G.GOM.slider.currentIdx = GOMidx;
4899 GalleryThumbnailSliderBuildAndStart(); // image slider on last displayed thumbnail
4900 // GalleryThumbnailSliderSetNextContent();
4901 }
4902 }
4903 }
4904 else {
4905 // reset slider content to initial content because all thumbnails are displayed
4906 GalleryThumbnailSliderSetContent( G.GOM.slider.hostItem );
4907 G.GOM.slider.hostIdx = -1;
4908 }
4909
4910 G.GOM.lastDisplayedIdx = GOMidx;
4911 }
4912 }
4913
4914 }
4915
4916 // ---------------------
4917 // replace image on last thumbnails with not displayed ones (mode ROWS or FULLCONTENT with galleryLastRowFull enabled)
4918 // function GalleryLastThumbnailSlideImage() {
4919 function GalleryThumbnailSliderBuildAndStart() {
4920
4921 if( G.O.thumbnailSliderDelay == 0 || G.GOM.slider.hostIdx == -1 ) {
4922 return;
4923 }
4924 clearTimeout(G.GOM.slider.timerID);
4925
4926 var item = G.GOM.slider.hostItem;
4927
4928 // dupplicate image layer -> for the next image
4929 if( item.$getElt('.nGY2TnImgNext').length == 0 ) {
4930 item.$getElt('.nGY2TnImg').clone().removeClass('nGY2TnImg').addClass('nGY2TnImgNext').insertAfter(item.$getElt('.nGY2TnImg'));
4931 item.$getElt('.nGY2TnImgBack').clone().removeClass('nGY2TnImgBack').addClass('nGY2TnImgBackNext').insertAfter(item.$getElt('.nGY2TnImg', true));
4932 item.$getElt('.nGY2GThumbnailImage', true); // important -> refresh the cache
4933 item.$getElt('.nGY2GThumbnailImg', true); // important -> refresh the cache
4934 }
4935
4936 item.CSSTransformSet('.nGY2TnImgNext', 'translateX', '100%', true);
4937 item.CSSTransformApply( '.nGY2TnImgNext' );
4938 item.CSSTransformSet('.nGY2TnImgBackNext', 'translateX', '100%', true);
4939 item.CSSTransformApply( '.nGY2TnImgBackNext' );
4940
4941 GalleryThumbnailSliderSetNextContent();
4942
4943 // clearTimeout(G.GOM.slider.timerID);
4944 // G.GOM.slider.timerID = setTimeout(function(){ GalleryThumbnailSliderStartTransition() }, G.O.thumbnailSliderDelay);
4945 G.GOM.slider.timerID = requestTimeout(function(){ GalleryThumbnailSliderStartTransition() }, G.O.thumbnailSliderDelay);
4946 }
4947
4948
4949 function GalleryThumbnailSliderSetNextContent() {
4950
4951 G.GOM.slider.nextIdx++;
4952 if( G.GOM.slider.nextIdx >= G.GOM.items.length ) {
4953 G.GOM.slider.nextIdx = G.GOM.slider.hostIdx;
4954 }
4955
4956 // new image
4957 var newItem = G.GOM.NGY2Item(G.GOM.slider.nextIdx);
4958 // var imgBlurred = G.emptyGif;
4959 var bgImg = "url('" + G.emptyGif + "')";
4960 if( newItem.imageDominantColors != null ) {
4961 // imgBlurred = newItem.imageDominantColors;
4962 bgImg = "url('" + newItem.imageDominantColors + "')";
4963 }
4964 G.GOM.slider.hostItem.$getElt('.nGY2TnImgBackNext', true).css({'background-image': bgImg, opacity: 1 });
4965 G.GOM.slider.hostItem.$getElt('.nGY2TnImgNext', true).css({ 'background-image': "url('" + newItem.thumbImg().src + "')", opacity: 1 });
4966 G.GOM.slider.hostItem.$getElt('.nGY2TnImgNext .nGY2GThumbnailImg', true).attr('src', newItem.thumbImg().src );
4967
4968
4969 }
4970
4971 // thumbnail slider - transition from one image to the next one
4972 function GalleryThumbnailSliderStartTransition() {
4973
4974 if( G.GOM.slider.hostItem.$getElt() != null ) {
4975
4976 // slider transition
4977 var tweenable = new NGTweenable();
4978 G.GOM.slider.tween = tweenable;
4979 tweenable.tween({
4980 from: { 'left': 100 },
4981 to: { 'left': 0 },
4982 duration: 800,
4983 delay: 0,
4984 // easing: 'easeInOutQuad',
4985 easing: 'easeOutQuart',
4986
4987 step: function (state) {
4988 if( G.GOM.slider.hostItem.$getElt() == null ) {
4989 // the thumbnail may have been destroyed since the start of the animation
4990 G.GOM.slider.tween.stop(false);
4991 return;
4992 }
4993
4994 // window.ng_draf( function() {
4995 // slide current content
4996 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgBack', 'translateX', -(100 - state.left) + '%');
4997 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgBack' );
4998 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImg', 'translateX', -(100 - state.left) + '%');
4999 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImg' );
5000
5001 // slide new content
5002 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgBackNext', 'translateX', state.left + '%');
5003 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgBackNext' );
5004 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgNext', 'translateX', state.left + '%');
5005 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgNext' );
5006 // });
5007
5008
5009 },
5010 finish: function (state) {
5011 if( G.GOM.slider.hostItem.$getElt() == null ) {
5012 // the thumbnail may be destroyed since the start of the animation
5013 return;
5014 }
5015
5016 if( G.GOM.NGY2Item(G.GOM.slider.nextIdx) == null ) { return; } // item does not exist anymore
5017
5018 // window.ng_draf( function() {
5019 // set new content as current content
5020 GalleryThumbnailSliderSetContent( G.GOM.NGY2Item(G.GOM.slider.nextIdx) );
5021 G.GOM.slider.currentIdx = G.GOM.slider.nextIdx;
5022 GalleryThumbnailSliderSetNextContent();
5023
5024 clearTimeout(G.GOM.slider.timerID);
5025 // G.GOM.slider.timerID=setTimeout(function(){ GalleryThumbnailSliderStartTransition() }, G.O.thumbnailSliderDelay);
5026 G.GOM.slider.timerID = requestTimeout(function(){ GalleryThumbnailSliderStartTransition() }, G.O.thumbnailSliderDelay);
5027 // });
5028 }
5029 });
5030 }
5031 }
5032
5033 // set main content of the thumbnail hosting the slider
5034 // hide the elements for the next content of the slider
5035 function GalleryThumbnailSliderSetContent( ngy2itemContent ) {
5036 if( G.GOM.slider.hostIdx == -1 ) { return; }
5037
5038 if( G.GOM.slider.tween != null ) {
5039 if( G.GOM.slider.tween._isTweening == true ) {
5040 G.GOM.slider.tween.stop(false);
5041 }
5042 }
5043
5044 var bgImg = "url('" + G.emptyGif + "')";
5045 if( ngy2itemContent.imageDominantColors != null ) {
5046 bgImg = "url('" + ngy2itemContent.imageDominantColors + "')";
5047 }
5048 G.GOM.slider.hostItem.$getElt('.nGY2TnImgBack').css('background-image', bgImg);
5049 G.GOM.slider.hostItem.$getElt('.nGY2TnImg').css('background-image', "url('" + ngy2itemContent.thumbImg().src + "')" );
5050 G.GOM.slider.hostItem.$getElt('.nGY2TnImg .nGY2GThumbnailImg').attr('src', ngy2itemContent.thumbImg().src );
5051
5052 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgBack', 'translateX', '0');
5053 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgBack' );
5054 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImg', 'translateX', '0');
5055 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImg' );
5056
5057 // place the containers for the next image slider outside of the thumbnail (=hidden)
5058 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgBackNext', 'translateX', '100%', true);
5059 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgBackNext' );
5060 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgNext', 'translateX', '100%', true);
5061 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgNext' );
5062
5063 // set new title and description
5064 if( G.O.thumbnailLabel.get('display') == true ) {
5065 var icons = G.O.icons.thumbnailAlbum;
5066 if( ngy2itemContent.kind != 'album' ) {
5067 icons = G.O.icons.thumbnailImage;
5068 }
5069 G.GOM.slider.hostItem.$getElt('.nGY2GThumbnailTitle').html(icons + getThumbnailTitle(ngy2itemContent));
5070 G.GOM.slider.hostItem.$getElt('.nGY2GThumbnailDescription').html(icons + getTumbnailDescription(ngy2itemContent));
5071 }
5072 }
5073
5074
5075
5076 // Compute the height of the label part of a thumbnail (title+description, both single line)
5077 function ThumbnailGetLabelHeight() {
5078 var newElt = [],
5079 newEltIdx = 0;
5080
5081 // if( G.O.thumbnailLabel.get('display') == false && G.tn.toolbar.getWidth(item) <= 0 ) {
5082 if( G.O.thumbnailLabel.get('display') == false ) {
5083 return 0;
5084 }
5085
5086 // var desc='';
5087 // if( G.O.thumbnailLabel.get('displayDescription') == true ) {
5088 // desc = 'aAzZjJ';
5089 // }
5090
5091 // visibility set to hidden
5092 newElt[newEltIdx++] = '<div class="nGY2GThumbnail ' + G.O.theme + '" style="display:block;visibility:hidden;position:absolute;top:-9999px;left:-9999px;" ><div class="nGY2GThumbnailSub">';
5093 if( G.O.thumbnailLabel.get('display') == true ) {
5094 // Labels: title and description
5095 newElt[newEltIdx++] = ' <div class="nGY2GThumbnailLabel" '+ G.tn.style.getLabel() +'>';
5096 newElt[newEltIdx++] = ' <div class="nGY2GThumbnailAlbumTitle" '+G.tn.style.getTitle()+'>aAzZjJ</div>';
5097 if( G.O.thumbnailLabel.get('displayDescription') == true ) {
5098 newElt[newEltIdx++] = ' <div class="nGY2GThumbnailDescription" '+G.tn.style.getDesc()+'>'+'aAzZjJ'+'</div>';
5099 }
5100 newElt[newEltIdx++] = ' </div>';
5101 }
5102
5103 newElt[newEltIdx++] = '</div></div>';
5104
5105 var $newDiv = jQuery(newElt.join('')).appendTo(G.$E.conTn);
5106 var h = $newDiv.find('.nGY2GThumbnailLabel').outerHeight(true);
5107 $newDiv.remove();
5108
5109 return h;
5110 }
5111
5112 function ThumbnailBuildStacks( bgColor ) {
5113 var ns=G.tn.opt.Get('stacks');
5114 if( ns == 0 ) { return ''; }
5115
5116 var s='';
5117 for( var i=0; i<ns; i++ ) {
5118 s='<div class="nGY2GThumbnailStack " style="display:none;'+bgColor+'"></div>'+s;
5119 }
5120 return s;
5121 }
5122
5123 //----- Build one UP thumbnail (=navigation thumbnail)
5124 function ThumbnailBuildAlbumpUp( item, GOMidx ) {
5125 // function ThumbnailBuildAlbumpUp( item, idx, GOMidx ) {
5126 var newElt = [],
5127 newEltIdx = 0;
5128
5129 var mp = '';
5130 if( G.O.thumbnailOpenInLightox === false ) {
5131 mp = 'cursor:default;'
5132 }
5133
5134 newElt[newEltIdx++] = ThumbnailBuildStacks('') + '<div class="nGY2GThumbnail" style="display:none;opacity:0;' + mp + '" >';
5135 newElt[newEltIdx++] = ' <div class="nGY2GThumbnailSub">';
5136
5137 var h=G.tn.defaultSize.getHeight(),
5138 w=G.tn.defaultSize.getWidth();
5139
5140 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>';
5141 // newElt[newEltIdx++] = ' <div class="nGY2GThumbnailAlbumUp" style="width:'+w+'px;height:'+h+'px;">'+G.O.icons.thumbnailAlbumUp+'</div>';
5142 newElt[newEltIdx++] = ' <div class="nGY2GThumbnailAlbumUp" >'+G.O.icons.thumbnailAlbumUp+'</div>';
5143 newElt[newEltIdx++] = ' </div>';
5144 newElt[newEltIdx++] = '</div>';
5145
5146 var $newDiv = jQuery(newElt.join('')).appendTo(G.$E.conTn); //.animate({ opacity: 1},1000, 'swing'); //.show('slow'); //.fadeIn('slow').slideDown('slow');
5147
5148 item.$elt = $newDiv;
5149 $newDiv.data('index', GOMidx);
5150 item.$getElt('.nGY2GThumbnailImg').data('index', GOMidx);
5151
5152 return;
5153 }
5154
5155
5156 //----- Build one thumbnail
5157 function ThumbnailBuild( item, idx, GOMidx ) {
5158 // function ThumbnailBuild( item, idx, GOMidx, lastOne ) {
5159 item.eltTransform = [];
5160 item.eltFilter = [];
5161 item.hoverInitDone = false;
5162 item.$Elts = [];
5163
5164 if( item.kind == 'albumUp' ) {
5165 ThumbnailBuildAlbumpUp( item, GOMidx);
5166 return;
5167 }
5168
5169 var newElt = [],
5170 newEltIdx = 0;
5171
5172 var mp = '';
5173 if( G.O.thumbnailOpenInLightox === false ) {
5174 mp = 'cursor:default;'
5175 }
5176
5177 // var src = encodeURI(item.thumbImg().src),
5178
5179 var src = (item.thumbImg().src).replace(/'/g, "%27"), // replace single quote with %27
5180 sTitle = getThumbnailTitle(item);
5181
5182 // image background -> visible during image download
5183 var bg = '';
5184 var bgImg = "background-image: url('" + G.emptyGif + "');";
5185 if( item.imageDominantColors != null ) {
5186 // dominant colorS (blurred preview image)
5187 bgImg = "background-image: url('" + item.imageDominantColors + "');";
5188 }
5189 else {
5190 // dominant color -> background color
5191 if( item.imageDominantColor != null ) {
5192 bg = 'background-color:' + item.imageDominantColor + ';';
5193 }
5194 else {
5195 bgImg = '';
5196 }
5197 }
5198
5199 var op = 'opacity:1;';
5200 if( G.O.thumbnailWaitImageLoaded == true ) {
5201 op = 'opacity:0;';
5202 }
5203
5204 // ##### thumbnail containers (with stacks)
5205 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" : "" ) + '">';
5206
5207
5208 // image size
5209 var w = G.tn.settings.getW();
5210 var h = G.tn.settings.getH();
5211 if( G.tn.settings.getMosaic() !== null ) {
5212 // mosaic layout ->
5213 w = G.GOM.items[GOMidx].width;
5214 h = G.GOM.items[GOMidx].height;
5215 }
5216
5217 var bgSize = 'contain';
5218 if( G.tn.opt.Get('crop') ) {
5219 bgSize = 'cover'; // thumbnail image will be cropped to fit in the thumbnail (no black border)
5220 }
5221
5222 // ##### layer for image background (color, dominant color, blurred preview)
5223 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;";
5224 newElt[newEltIdx++]='<div class="nGY2GThumbnailImage nGY2TnImgBack" style="' + s1 + '"></div>';
5225
5226 // #### layer for image
5227 // for url in CSS: single backslashes are replaced by double backslashes
5228 var s2 = op + "position: absolute; top: 0px; left: 0px; width:" + w + "px; height:" + h + "px; background-image: url('" + src.replace(/\\/g, '\\\\') + "'); background-position: center center; background-repeat: no-repeat; background-size:" + bgSize + "; overflow: hidden;";
5229 newElt[newEltIdx++]='<div class="nGY2GThumbnailImage nGY2TnImg" style="' + s2 + '">';
5230 newElt[newEltIdx++]=' <img class="nGY2GThumbnailImg nGY2TnImg2" src="' + src + '" alt="' + sTitle + '" style="opacity:0;" data-idx="' + idx + '" data-albumidx="' + G.GOM.albumIdx + '" >';
5231 newElt[newEltIdx++]='</div>';
5232
5233 // ##### layer for user customization purposes
5234 newElt[newEltIdx++]='<div class="nGY2GThumbnailCustomLayer"></div>';
5235
5236 // ##### layer for labels (title + description and their icons)
5237 if( G.O.thumbnailLabel.get('display') == true ) {
5238 // Labels: title and description
5239 newElt[newEltIdx++]= ' <div class="nGY2GThumbnailLabel" '+ G.tn.style.getLabel(item) + '>';
5240 if( item.kind == 'album' ) {
5241 // album kind
5242 newElt[newEltIdx++]= ' <div class="nGY2GThumbnailTitle nGY2GThumbnailAlbumTitle" ' + G.tn.style.getTitle() + '>' + G.O.icons.thumbnailAlbum + sTitle + '</div>';
5243 }
5244 else {
5245 // image/media kind
5246 newElt[newEltIdx++]= ' <div class="nGY2GThumbnailTitle nGY2GThumbnailImageTitle" ' + G.tn.style.getTitle() + '>' + G.O.icons.thumbnailImage + sTitle + '</div>';
5247 }
5248 newElt[newEltIdx++]= ' <div class="nGY2GThumbnailDescription" ' + G.tn.style.getDesc() + '>' + getTumbnailDescription(item) + '</div>';
5249 newElt[newEltIdx++]= ' </div>';
5250 }
5251
5252 // ##### layer for tools
5253 // newElt[newEltIdx++] = ThumbnailBuildTools(item, lastOne);
5254 newElt[newEltIdx++] = ThumbnailBuildTools(item);
5255
5256 // close containers
5257 newElt[newEltIdx++]='</div></div>';
5258
5259 var $newDiv =jQuery(newElt.join('')).appendTo(G.$E.conTn);
5260
5261 item.$elt=$newDiv;
5262 $newDiv.data('index',GOMidx);
5263 item.$getElt('.nGY2GThumbnailImg').data('index',GOMidx);
5264
5265 // Custom init function
5266 var fu=G.O.fnThumbnailInit;
5267 if( fu !== null ) {
5268 typeof fu == 'function' ? fu($newDiv, item, GOMidx) : window[fu]($newDiv, item, GOMidx);
5269 }
5270
5271 if( item.title != 'image gallery by nanogallery2 [build]' ) {
5272 ThumbnailOverInit(GOMidx);
5273 }
5274
5275 return ;
5276 }
5277
5278
5279 // Thumbnail layer for tools (toolbars and counter)
5280 function ThumbnailBuildTools( item ) {
5281
5282 // toolbars
5283 var tb = ThumbnailBuildToolbarOne(item, 'topLeft') + ThumbnailBuildToolbarOne(item, 'topRight') + ThumbnailBuildToolbarOne(item, 'bottomLeft') + ThumbnailBuildToolbarOne(item, 'bottomRight');
5284
5285 // counter of not displayed images
5286 tb += '<div class="nGY2GThumbnailIconsFullThumbnail"></div>';
5287
5288 return tb;
5289 }
5290
5291 function ThumbnailBuildToolbarOne( item, position ) {
5292 var toolbar = '';
5293 var tb = G.tn.toolbar.get(item);
5294 var width = { xs:0, sm:1, me:2, la:3, xl:4 };
5295 var cnt = 0;
5296
5297 if( tb[position] != '' ) {
5298 var pos='top: 0; right: 0; text-align: right;'; // 'topRight' and default
5299 switch( position ) {
5300 case 'topLeft':
5301 pos = 'top: 0; left: 0; text-align: left;';
5302 break;
5303 case 'bottomRight':
5304 pos = 'bottom: 0; right: 0; text-align: right;';
5305 break;
5306 case 'bottomLeft':
5307 pos = 'bottom: 0; left: 0; text-align: left;';
5308 break;
5309 }
5310
5311 toolbar += ' <ul class="nGY2GThumbnailIcons" style="' + pos + '">';
5312
5313 var icons = tb[position].split(',');
5314 var nb = icons.length;
5315 for( var i = 0; i < nb; i++ ) {
5316 var icon = icons[i].replace(/^\s*|\s*$/, ''); //trim trailing/leading whitespace
5317
5318 var minWidth = icon.substring(0,2).toLowerCase();
5319 var tIcon = icon;
5320 var display = true;
5321 if( /xs|sm|me|la|xl/i.test(minWidth) ) {
5322 // check visbility (depending on screen width)
5323 if( width[minWidth] > width[G.GOM.curWidth] ) {
5324 display = false;
5325 }
5326 tIcon = icon.substring(2);
5327 }
5328
5329 if( display ) {
5330 var sp=(i+1<nb ? '&nbsp;' :'');
5331 switch( tIcon ) {
5332 case 'COUNTER':
5333 if( item.kind == 'album' ) {
5334 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="">';
5335 toolbar += ' <div class="nGY2GThumbnailIconImageCounter"></div>';
5336 toolbar += ' <div class="nGY2GThumbnailIconText">' + G.O.icons.thumbnailCounter+Math.max((item.getContentLength(false)),item.numberItems) + sp + '</div>';
5337 toolbar += ' </li>';
5338 cnt++;
5339 }
5340 break;
5341 case 'COUNTER2':
5342 if( item.kind == 'album' ) {
5343 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="">';
5344 toolbar += ' <div class="nGY2GThumbnailIconTextBadge">' + G.O.icons.thumbnailCounter+Math.max((item.getContentLength(false)),item.numberItems) + sp + '</div>';
5345 toolbar += ' </li>';
5346 cnt++;
5347 }
5348 break;
5349 case 'SHARE':
5350 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="' + tIcon + '">';
5351 toolbar += ' <div>' + G.O.icons.thumbnailShare + '</div>';
5352 toolbar += ' </li>';
5353 cnt++;
5354 break;
5355 case 'DOWNLOAD':
5356 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="' + tIcon + '">';
5357 toolbar += ' <div>' + G.O.icons.thumbnailDownload + '</div>';
5358 toolbar += ' </li>';
5359 cnt++;
5360 break;
5361 case 'INFO':
5362 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="' + tIcon + '">';
5363 toolbar += ' <div>' + G.O.icons.thumbnailInfo + '</div>';
5364 toolbar += ' </li>';
5365 cnt++;
5366 break;
5367 case 'SHOPPINGCART':
5368 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="' + tIcon + '">';
5369 // toolbar += ' <div>' + G.O.icons.thumbnailShoppingcart + '</div>';
5370 toolbar += ThumbnailBuildToolbarOneCart( item );
5371
5372 toolbar += ' </li>';
5373 cnt++;
5374 break;
5375 case 'DISPLAY':
5376 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="DISPLAY">';
5377 toolbar += ' <div class="nGY2GThumbnailIconImageShare">' + G.O.icons.thumbnailDisplay + '</div>';
5378 toolbar += ' </li>';
5379 cnt++;
5380 break;
5381 case 'CUSTOM1':
5382 case 'CUSTOM2':
5383 case 'CUSTOM3':
5384 case 'CUSTOM4':
5385 case 'CUSTOM5':
5386 case 'CUSTOM6':
5387 case 'CUSTOM7':
5388 case 'CUSTOM8':
5389 case 'CUSTOM9':
5390 case 'CUSTOM10':
5391 var cust = tIcon.replace('CUSTOM', '');
5392 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="' + tIcon.toLowerCase() + '">';
5393 toolbar += ' <div class="nGY2GThumbnailIconImageShare">' + G.O.icons['thumbnailCustomTool' + cust] + '</div>';
5394 toolbar += ' </li>';
5395 cnt++;
5396 break;
5397 case 'FEATURED':
5398 if( item.featured === true ) {
5399 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="">';
5400 toolbar += ' <div class="nGY2GThumbnailIconImageFeatured">' + G.O.icons.thumbnailFeatured + '</div>';
5401 toolbar += ' </li>';
5402 cnt++;
5403 }
5404 break;
5405 case 'SELECT':
5406 if( G.O.thumbnailSelectable == true ) {
5407 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="TOGGLESELECT">';
5408 if( item.selected === true ) {
5409 toolbar += ' <div class="nGY2GThumbnailIconImageSelect nGY2ThumbnailSelected">' + G.O.icons.thumbnailSelected + '</div>';
5410 }
5411 else {
5412 toolbar += ' <div class="nGY2GThumbnailIconImageSelect nGY2ThumbnailUnselected">' + G.O.icons.thumbnailUnselected + '</div>';
5413 }
5414 toolbar += ' </li>';
5415 cnt++;
5416 }
5417 break;
5418 }
5419 }
5420 }
5421 toolbar += ' </ul>';
5422 }
5423
5424 if( cnt > 0 ) {
5425 return toolbar;
5426 }
5427 else {
5428 return '';
5429 }
5430 }
5431
5432 // CART ICON AND COUNTER
5433 function ThumbnailBuildToolbarOneCart( item ) {
5434 var q = 0;
5435
5436 var id = item.GetID()
5437 for( var i=0; i<G.shoppingCart.length; i++ ) {
5438 if( G.I[G.shoppingCart[i].idx].GetID() == id ) {
5439 q = G.shoppingCart[i].qty;
5440 }
5441 }
5442 if( q == 0 ) {
5443 q = '';
5444 }
5445
5446 return ' <div>' + G.O.icons.thumbnailShoppingcart + q + '</div>';
5447 }
5448 function ThumbnailBuildToolbarOneCartUpdate( item ) {
5449 var $e = item.$elt;
5450
5451 if( $e != null ) {
5452 var $q = $e.find('*[data-ngy2action="SHOPPINGCART"]');
5453 if( $q !== undefined ) {
5454 $q.html( ThumbnailBuildToolbarOneCart( item ) );
5455 }
5456 }
5457 }
5458
5459 function getThumbnailTitle( item ) {
5460
5461 var sTitle = item.title;
5462 if( G.O.thumbnailLabel.get('display') == true ) {
5463 if( sTitle === undefined || sTitle.length == 0 ) { sTitle = '&nbsp;'; }
5464
5465 if( G.i18nTranslations.thumbnailImageTitle != '' ) {
5466 sTitle = G.i18nTranslations.thumbnailImageTitle;
5467 }
5468 var ml = G.O.thumbnailLabel.get('titleMaxLength');
5469 if( ml > 3 && sTitle.length > ml ){
5470 sTitle = sTitle.substring(0, ml) + '...';
5471 }
5472 }
5473
5474 return sTitle;
5475 }
5476
5477 function getTumbnailDescription( item ) {
5478 var sDesc = '';
5479 if( G.O.thumbnailLabel.get('displayDescription') == true ) {
5480 if( item.kind == 'album' ) {
5481 if( G.i18nTranslations.thumbnailImageDescription != '' ) {
5482 sDesc = G.i18nTranslations.thumbnailAlbumDescription;
5483 }
5484 else {
5485 sDesc = item.description;
5486 }
5487 }
5488 else {
5489 if( G.i18nTranslations.thumbnailImageDescription != '' ) {
5490 sDesc = G.i18nTranslations.thumbnailImageDescription;
5491 }
5492 else {
5493 sDesc = item.description;
5494 }
5495 }
5496 var ml = G.O.thumbnailLabel.get('descriptionMaxLength');
5497 if( ml > 3 && sDesc.length > ml ){
5498 sDesc = sDesc.substring(0, ml) + '...';
5499 }
5500 if( sDesc.length == 0 ) {
5501 sDesc = '&nbsp;';
5502 }
5503 }
5504
5505 return sDesc;
5506 }
5507
5508
5509
5510 // Retrieve the maximum number of thumbnails that fits in one row
5511 function NbThumbnailsPerRow( areaWidth ) {
5512 var tnW = G.tn.defaultSize.getOuterWidth();
5513
5514 var nbMaxTn = 0;
5515 if( G.O.thumbnailAlignment == 'justified' ) {
5516 nbMaxTn = Math.floor((areaWidth)/(tnW));
5517 }
5518 else {
5519 nbMaxTn = Math.floor((areaWidth + G.tn.settings.GetResponsive('gutterWidth'))/(tnW + G.tn.settings.GetResponsive('gutterWidth')));
5520 }
5521
5522 if( G.O.maxItemsPerLine >0 && nbMaxTn > G.O.maxItemsPerLine ) {
5523 nbMaxTn = G.O.maxItemsPerLine;
5524 }
5525
5526 if( nbMaxTn < 1 ) { nbMaxTn = 1; }
5527
5528 return nbMaxTn
5529 }
5530
5531 // Thumbnail display animation
5532 function ThumbnailAppear( n, cnt ) {
5533 var curTn = G.GOM.items[n];
5534 var item = G.I[curTn.thumbnailIdx];
5535
5536
5537 if( G.tn.opt.Get('displayTransition') == 'NONE' ) {
5538 item.$elt.css({ opacity: 1 });
5539 ThumbnailAppearFinish( item );
5540 }
5541 else {
5542 if( item.$elt == null ) { return; }
5543 var top = G.GOM.cache.containerOffset.top + ( curTn.top - G.GOM.clipArea.top );
5544 var vp = G.GOM.cache.viewport;
5545 if( (top + (curTn.top - G.GOM.clipArea.top)) >= (vp.t - 50) && top <= (vp.t + vp.h + 50) ) {
5546 // display animation only if in the current viewport
5547 var delay = cnt * G.tn.opt.Get('displayInterval');
5548 if( G.tn.opt.Get('displayTransition') == 'CUSTOM' ) {
5549 if( G.GOM.curNavLevel == 'lN' ) {
5550 G.O.fnThumbnailDisplayEffect(item.$elt, item, n, delay);
5551 }
5552 else {
5553 G.O.fnThumbnailL1DisplayEffect(item.$elt, item, n, delay);
5554 }
5555 }
5556 else {
5557 G.GOM.thumbnails2Display.push({itm: item, d: delay});
5558 // ThumbnailDisplayAnim2(item, delay);
5559 }
5560 return;
5561 }
5562 else {
5563 item.$elt.css({ opacity: 1 });
5564 ThumbnailAppearFinish(item);
5565 }
5566 }
5567 }
5568
5569
5570 // displays thumbnail stacks at the end of the display animation
5571 function ThumbnailAppearFinish( item ) {
5572
5573 // add stacks
5574 var ns = G.tn.opt.Get('stacks');
5575 if( ns > 0 ) {
5576 // display stacks
5577 item.$elt.css({ display: 'block'});
5578 var o = 0.9;
5579 // set stack opacity
5580 for( var i = ns-1; i>=0; i-- ) {
5581 item.$elt.eq(i).css('opacity', o);
5582 o = o - 0.2;
5583 }
5584
5585 }
5586 }
5587
5588
5589 function ThumbnailDisplayAnim2( item, delay ) {
5590 function randomIntFromInterval(min,max) {
5591 return Math.floor(Math.random()*(max-min+1)+min);
5592 }
5593 var oFrom = {};
5594 var oTo = {};
5595
5596 switch (G.tn.opt.Get('displayTransition')) {
5597 case 'RANDOMSCALE': {
5598 var scales = [0.95, 1, 1.05, 1.1];
5599 var zi = [1, 2, 3, 4];
5600
5601 var r = randomIntFromInterval(0,3);
5602 while( r == G.GOM.lastRandomValue ) {
5603 r = randomIntFromInterval(0,3);
5604 }
5605 G.GOM.lastRandomValue = r;
5606 let f = scales[r];
5607 // item.$elt.css({ 'z-index': G.GOM.lastZIndex+zi[r], 'box-shadow': '-1px 2px 5px 1px rgba(0, 0, 0, 0.7)' });
5608 item.$elt.css({ 'z-index': G.GOM.lastZIndex+zi[r], 'box-shadow': '0px 0px 5px 3px rgba(0,0,0,0.74)' });
5609
5610 oFrom = { scale: 0.5, opacity:0 };
5611 oTo = { scale: f, opacity:1 };
5612 break;
5613 }
5614
5615 case 'SCALEUP': {
5616 let f = G.tn.opt.Get('displayTransitionStartVal');
5617 if( f == 0 ) { f = 0.6; } // default value
5618 oFrom = { scale: f, opacity: 0 };
5619 oTo = { scale: 1, opacity: 1 };
5620 break;
5621 }
5622
5623 case 'SCALEDOWN': {
5624 let f = G.tn.opt.Get('displayTransitionStartVal');
5625 if( f == 0 ) { f=1.3; } // default value
5626 oFrom = { scale: f, opacity: 0 };
5627 oTo = { scale: 1, opacity: 1 };
5628 break;
5629 }
5630 case 'SLIDEUP': {
5631 let f = G.tn.opt.Get('displayTransitionStartVal');
5632 if( f == 0 ) { f=50; } // default value
5633 oFrom = { opacity: 0, translateY: f };
5634 oTo = { opacity: 1, translateY: 0 };
5635 break;
5636 }
5637 case 'SLIDEDOWN': {
5638 let f=G.tn.opt.Get('displayTransitionStartVal');
5639 if( f == 0 ) { f=-50; } // default value
5640 oFrom = { opacity: 0, translateY: f };
5641 oTo = { opacity: 1, translateY: 0 };
5642 break;
5643 }
5644 case 'FLIPUP': {
5645 let f=G.tn.opt.Get('displayTransitionStartVal');
5646 if( f == 0 ) { f=100; } // default value
5647 oFrom = { opacity: 0, translateY: f, rotateX: 45 };
5648 oTo = { opacity: 1, translateY: 0, rotateX: 0 };
5649 break;
5650 }
5651 case 'FLIPDOWN': {
5652 let f=G.tn.opt.Get('displayTransitionStartVal');
5653 if( f == 0 ) { f=-100; } // default value
5654 oFrom = { opacity: 0, translateY: f, rotateX: -45 };
5655 oTo = { opacity: 1, translateY: 0, rotateX: 0 };
5656 break;
5657 }
5658 case 'SLIDEUP2': {
5659 let f = G.tn.opt.Get('displayTransitionStartVal');
5660 if( f == 0 ) { f=100; } // default value
5661 oFrom = { opacity: 0, translateY: f, rotateY: 40 };
5662 oTo = { opacity: 1, translateY: 0, rotateY: 0 };
5663 break;
5664 }
5665 case 'IMAGESLIDEUP': {
5666 // let f = G.tn.opt.Get('displayTransitionStartVal');
5667 // if( f == 0 ) { f=100; } // default value
5668 oFrom = { opacity: 0, top: '100%' };
5669 oTo = { opacity: 1, top: '0%' };
5670 break;
5671 }
5672 case 'SLIDEDOWN2': {
5673 let f=G.tn.opt.Get('displayTransitionStartVal');
5674 if( f == 0 ) { f=-100; } // default value
5675 oFrom = { opacity: 0, translateY: f, rotateY: 40 };
5676 oTo = { opacity: 1, translateY: 0, rotateY: 0 };
5677 break;
5678 }
5679 case 'SLIDERIGHT': {
5680 let f=G.tn.opt.Get('displayTransitionStartVal');
5681 if( f == 0 ) { f=-150; } // default value
5682 oFrom = { opacity: 0, translateX: f };
5683 oTo = { opacity: 1, translateX: 0 };
5684 break;
5685 }
5686 case 'SLIDELEFT': {
5687 let f=G.tn.opt.Get('displayTransitionStartVal');
5688 if( f == 0 ) { f=150; } // default value
5689 oFrom = { opacity: 0, translateX: f };
5690 oTo = { opacity: 1, translateX: 0 };
5691 break;
5692 }
5693 case 'FADEIN':
5694 oFrom = { opacity: 0 };
5695 oTo = { opacity: 1 };
5696 break;
5697
5698
5699 }
5700
5701 var tweenable = new NGTweenable();
5702 tweenable.tween({
5703 from: oFrom,
5704 to: oTo,
5705 attachment: { $e:item.$elt, item: item, tw: tweenable },
5706 delay: delay,
5707 duration: G.tn.opt.Get('displayTransitionDuration'),
5708 easing: G.tn.opt.Get('displayTransitionEasing'),
5709 step: function (state, att) {
5710 window.requestAnimationFrame( function() {
5711 if( att.item.$elt === null ) { // the thumbnail may have been destroyed since the start of the animation
5712 att.tw.stop(false);
5713 return;
5714 }
5715 switch (G.tn.opt.Get('displayTransition')) {
5716 case 'RANDOMSCALE':
5717 att.$e.css( G.CSStransformName , 'scale(' + state.scale + ')').css('opacity', state.opacity);
5718 break;
5719 case 'SCALEUP':
5720 att.$e.css( G.CSStransformName , 'scale('+state.scale+')').css('opacity',state.opacity);
5721 break;
5722 case 'SCALEDOWN':
5723 att.item.$elt.last().css('opacity', state.opacity);
5724 att.item.CSSTransformSet('.nGY2GThumbnail', 'scale', state.scale);
5725 att.item.CSSTransformApply('.nGY2GThumbnail');
5726 break;
5727 case 'SLIDEUP':
5728 att.item.$elt.css('opacity', state.opacity);
5729 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px, '+state.translateY + 'px');
5730 att.item.CSSTransformApply('.nGY2GThumbnail');
5731 break;
5732 case 'SLIDEDOWN':
5733 att.item.$elt.css('opacity', state.opacity);
5734 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px,'+state.translateY+'px');
5735 att.item.CSSTransformApply('.nGY2GThumbnail');
5736 break;
5737 case 'FLIPUP':
5738 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px,'+state.translateY+'px');
5739 att.item.CSSTransformSet('.nGY2GThumbnail', 'rotateX', state.rotateX+'deg');
5740 att.item.$elt.css('opacity', state.opacity);
5741 att.item.CSSTransformApply('.nGY2GThumbnail');
5742 break;
5743 case 'FLIPDOWN':
5744 att.item.$elt.css('opacity', state.opacity);
5745 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px,' + state.translateY + 'px');
5746 att.item.CSSTransformSet('.nGY2GThumbnail', 'rotateX', state.rotateX + 'deg');
5747 att.item.CSSTransformApply('.nGY2GThumbnail');
5748 break;
5749 case 'SLIDEUP2':
5750 att.item.$elt.css('opacity', state.opacity);
5751 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px,' + state.translateY + 'px');
5752 att.item.CSSTransformSet('.nGY2GThumbnail', 'rotateY', state.rotateY + 'deg');
5753 att.item.CSSTransformApply('.nGY2GThumbnail');
5754 break;
5755 case 'IMAGESLIDEUP':
5756 att.item.$elt.css('opacity', state.opacity);
5757 att.item.$Elts['.nGY2GThumbnailImage'].css('top', state.top);
5758 break;
5759 case 'SLIDEDOWN2':
5760 att.item.$elt.css('opacity', state.opacity);
5761 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px, ' + state.translateY + 'px');
5762 att.item.CSSTransformSet('.nGY2GThumbnail', 'rotateY', state.rotateY + 'deg');
5763 att.item.CSSTransformApply('.nGY2GThumbnail');
5764 break;
5765 case 'SLIDERIGHT':
5766 att.item.$elt.css('opacity', state.opacity);
5767 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', state.translateX + 'px, 0px');
5768 att.item.CSSTransformApply('.nGY2GThumbnail');
5769 break;
5770 case 'SLIDELEFT':
5771 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', state.translateX + 'px, 0px');
5772 att.item.$elt.css('opacity', state.opacity);
5773 att.item.CSSTransformApply('.nGY2GThumbnail');
5774 break;
5775 case 'FADEIN':
5776 att.$e.css(state);
5777 break;
5778 }
5779 });
5780 // att.$e.css( G.CSStransformName , 'scale('+state.scale+')').css('opacity',state.opacity);
5781 },
5782 finish: function (state, att) {
5783 window.requestAnimationFrame( function() {
5784 if( att.item.$elt === null ) { return; }
5785
5786 switch (G.tn.opt.Get('displayTransition')) {
5787 case 'RANDOMSCALE':
5788 att.$e.css( G.CSStransformName , 'scale('+state.scale+')').css('opacity', '');
5789 break;
5790 case 'SCALEUP':
5791 att.$e.css( G.CSStransformName , '').css('opacity', '');
5792 break;
5793 case 'SCALEDOWN':
5794 att.item.$elt.last().css('opacity', '');
5795 att.item.CSSTransformSet('.nGY2GThumbnail', 'scale', state.scale);
5796 att.item.CSSTransformApply('.nGY2GThumbnail');
5797 break;
5798 case 'IMAGESLIDEUP':
5799 att.item.$elt.css('opacity', '');
5800 att.item.$Elts['.nGY2GThumbnailImage'].css('top', 0);
5801 break;
5802 case 'SLIDEDOWN2':
5803 att.item.$elt.css('opacity', '');
5804 att.item.CSSTransformApply('.nGY2GThumbnail');
5805 break;
5806 default :
5807 // case 'SLIDEUP':
5808 // case 'SLIDEDOWN':
5809 // case 'FLIPUP':
5810 // case 'FLIPDOWN':
5811 // case 'SLIDEUP2':
5812 // case 'SLIDERIGHT':
5813 // case 'SLIDELEFT':
5814 // case 'FADEIN':
5815 att.item.$elt.css('opacity', '');
5816 }
5817 ThumbnailAppearFinish(att.item);
5818 });
5819
5820 }
5821 });
5822
5823 }
5824
5825 // batch display thumbnails with animation
5826 function ThumbnailDisplayAnimBatch() {
5827
5828 G.GOM.thumbnails2Display.forEach( function(one) {
5829 ThumbnailDisplayAnim2(one.itm, one.d);
5830 });
5831 G.GOM.thumbnails2Display=[];
5832 }
5833
5834
5835
5836 // ######################################
5837 // Gallery display animation
5838 function GalleryAppear() {
5839
5840 var d=G.galleryDisplayTransitionDuration.Get();
5841 switch( G.galleryDisplayTransition.Get() ){
5842 case 'ROTATEX':
5843 G.$E.base.css({ perspective: '1000px', 'perspective-origin': '50% 0%' });
5844 new NGTweenable().tween({
5845 from: { r: 50 },
5846 to: { r: 0 },
5847 attachment: { orgIdx: G.GOM.albumIdx },
5848 duration: d,
5849 easing: 'easeOutCirc',
5850 step: function (state, att) {
5851 if( att.orgIdx == G.GOM.albumIdx ) {
5852 // window.ng_draf( function() {
5853 G.$E.conTnParent.css( G.CSStransformName , 'rotateX(' + state.r + 'deg)');
5854 // });
5855 }
5856 }
5857 });
5858 break;
5859 case 'SLIDEUP':
5860 G.$E.conTnParent.css({ opacity: 0 });
5861 new NGTweenable().tween({
5862 from: { y: 200, o: 0 },
5863 to: { y: 0, o: 1 },
5864 attachment: { orgIdx: G.GOM.albumIdx },
5865 duration: d,
5866 easing: 'easeOutCirc',
5867 step: function (state, att) {
5868 if( att.orgIdx == G.GOM.albumIdx ) {
5869 // window.ng_draf( function() {
5870 G.$E.conTnParent.css( G.CSStransformName , 'translate( 0px, '+state.y + 'px)').css('opacity', state.o);
5871 // });
5872 }
5873 }
5874 });
5875 break;
5876 case 'NONE':
5877 default:
5878 break;
5879 }
5880
5881
5882 }
5883
5884 // ######################################
5885 // ##### THUMBNAIL HOVER MANAGEMENT #####
5886 // ######################################
5887
5888 function ThumbnailOverInit( GOMidx ) {
5889 // Over init in 2 step:
5890 // 1) init with thumbnailBuildInit2 parameter
5891 // 2) init with the hover effect parameter
5892
5893
5894 var curTn = G.GOM.items[GOMidx];
5895 var item = G.I[curTn.thumbnailIdx];
5896
5897 if( item.$elt == null ) { return; } // zombie
5898
5899 var fu = G.O.fnThumbnailHoverInit;
5900 if( fu !== null ) {
5901 typeof fu == 'function' ? fu($e, item, GOMidx) : window[fu]($e, item, GOMidx);
5902 }
5903
5904 // build initialization
5905 var inits = G.tn.buildInit.get();
5906 for( var j = 0; j < inits.length; j++) {
5907 switch( inits[j].property ) {
5908 // CSS Transform
5909 case 'scale':
5910 case 'rotateX':
5911 case 'rotateY':
5912 case 'rotateZ':
5913 case 'translateX':
5914 case 'translateY':
5915 case 'translateZ':
5916 item.CSSTransformSet(inits[j].element, inits[j].property, inits[j].value);
5917 item.CSSTransformApply(inits[j].element);
5918 break;
5919 // CSS filter
5920 case 'blur':
5921 case 'brightness':
5922 case 'grayscale':
5923 case 'sepia':
5924 case 'contrast':
5925 case 'opacity':
5926 case 'saturate':
5927 item.CSSFilterSet(inits[j].element, inits[j].property, inits[j].value);
5928 item.CSSFilterApply(inits[j].element);
5929 break;
5930 default:
5931 var $t=item.$getElt(inits[j].element);
5932 $t.css( inits[j].property, inits[j].value );
5933 break;
5934 }
5935 }
5936
5937 // hover
5938 var effects = G.tn.hoverEffects.get();
5939 for( var j = 0; j < effects.length; j++) {
5940 if( effects[j].firstKeyframe === true ) {
5941 switch( effects[j].type ) {
5942 case 'scale':
5943 case 'rotateX':
5944 case 'rotateY':
5945 case 'rotateZ':
5946 case 'translateX':
5947 case 'translateY':
5948 case 'translateZ':
5949 item.CSSTransformSet(effects[j].element, effects[j].type, effects[j].from);
5950 item.CSSTransformApply(effects[j].element);
5951 break;
5952 case 'blur':
5953 case 'brightness':
5954 case 'grayscale':
5955 case 'sepia':
5956 case 'contrast':
5957 case 'opacity':
5958 case 'saturate':
5959 item.CSSFilterSet(effects[j].element, effects[j].type, effects[j].from);
5960 item.CSSFilterApply(effects[j].element);
5961 break;
5962 default:
5963 var $t = item.$getElt(effects[j].element);
5964 $t.css( effects[j].type, effects[j].from );
5965 break;
5966
5967 }
5968 }
5969 }
5970 item.hoverInitDone=true;
5971 }
5972
5973 function ThumbnailHoverReInitAll() {
5974 if( G.GOM.albumIdx == -1 ) { return; };
5975 var l = G.GOM.items.length;
5976 for( var i = 0; i < l ; i++ ) {
5977 ThumbnailOverInit(i);
5978 // G.GOM.items[i].hovered=false;
5979 G.I[G.GOM.items[i].thumbnailIdx].hovered = false;
5980 }
5981 }
5982
5983
5984 function ThumbnailHover( GOMidx ) {
5985 if( G.GOM.albumIdx == -1 || !G.galleryResizeEventEnabled ) { return; };
5986 if( G.GOM.slider.hostIdx == GOMidx ) {
5987 // slider hosted on thumbnail -> no hover effect
5988 return;
5989 }
5990 var curTn = G.GOM.items[GOMidx];
5991 var item = G.I[curTn.thumbnailIdx];
5992 if( item.kind == 'albumUp' || item.$elt == null ) { return; }
5993
5994 item.hovered = true;
5995
5996 var fu = G.O.fnThumbnailHover;
5997 if( fu !== null ) {
5998 typeof fu == 'function' ? fu(item.$elt, item, GOMidx) : window[fu](item.$elt, item, GOMidx);
5999 }
6000 var effects = G.tn.hoverEffects.get();
6001
6002 try {
6003 for( var j = 0; j < effects.length; j++) {
6004 if( effects[j].hoverin === true ) {
6005 //item.animate( effects[j], j*10, true );
6006 item.animate( effects[j], 0, true );
6007 }
6008 }
6009 // effects on whole layout
6010 // GalleryResize( GOMidx );
6011 }
6012 catch (e) {
6013 NanoAlert(G, 'error on hover: ' + e.message );
6014 }
6015
6016 }
6017
6018 function ThumbnailHoverOutAll() {
6019 if( G.GOM.albumIdx == -1 ) { return; };
6020 var l = G.GOM.items.length;
6021 for( var i = 0; i < l ; i++ ) {
6022 if( G.GOM.items[i].inDisplayArea ) {
6023 ThumbnailHoverOut(i);
6024 }
6025 else {
6026 G.I[G.GOM.items[i].thumbnailIdx].hovered = false;
6027 }
6028 }
6029 }
6030
6031
6032 function ThumbnailHoverOut( GOMidx ) {
6033 if( G.GOM.albumIdx == -1 || !G.galleryResizeEventEnabled ) { return; }
6034
6035 if( G.GOM.slider.hostIdx == GOMidx ) {
6036 // slider on thumbnail -> no hover effect
6037 return;
6038 }
6039
6040 var curTn = G.GOM.items[GOMidx];
6041 var item = G.I[curTn.thumbnailIdx];
6042 if( item.kind == 'albumUp' || !item.hovered ) { return; }
6043 item.hovered = false;
6044 if( item.$elt == null ) { return; }
6045
6046 var fu = G.O.fnThumbnailHoverOut;
6047 if( fu !== null ) {
6048 typeof fu == 'function' ? fu(item.$elt, item, GOMidx) : window[fu](item.$elt, item, GOMidx);
6049 }
6050
6051 var effects = G.tn.hoverEffects.get();
6052 try {
6053 for( var j = 0; j < effects.length; j++) {
6054 if( effects[j].hoverout === true ) {
6055 // item.animate( effects[j], j*10, false );
6056 item.animate( effects[j], 0, false );
6057 }
6058 }
6059 // effects on whole layout
6060 // GalleryResize( );
6061 }
6062 catch (e) {
6063 NanoAlert(G, 'error on hoverOut: ' + e.message );
6064 }
6065
6066 }
6067
6068
6069 /** @function DisplayPhoto */
6070 function DisplayPhoto( imageID, albumID ) {
6071
6072 if( G.O.debugMode ) { console.log('#DisplayPhoto : '+ albumID +'-'+ imageID); }
6073 var albumIdx = NGY2Item.GetIdx(G, albumID);
6074 if( albumIdx == 0 ) {
6075 G.GOM.curNavLevel = 'l1';
6076 }
6077 else {
6078 G.GOM.curNavLevel = 'lN';
6079 }
6080
6081 if( albumIdx == -1 ) {
6082 // get content of album on root level
6083 if( G.O.kind != '' ) {
6084 // do not add album if Markup or Javascript data
6085 NGY2Item.New( G, '', '', albumID, '0', 'album' ); // create empty album
6086 // albumIdx = G.I.length - 1;
6087 }
6088 }
6089
6090 var ngy2ItemIdx = NGY2Item.GetIdx(G, imageID);
6091 if( ngy2ItemIdx == -1 ) {
6092 // get content of the album
6093 AlbumGetContent( albumID, DisplayPhoto, imageID, albumID );
6094 return;
6095 }
6096
6097 if( G.O.debugMode ) { console.log('#DisplayPhoto : '+ ngy2ItemIdx); }
6098
6099 DisplayPhotoIdx(ngy2ItemIdx);
6100
6101 }
6102
6103 // function AlbumGetContent( albumIdx, fnToCall ) {
6104 function AlbumGetContent( albumID, fnToCall, fnParam1, fnParam2 ) {
6105 // var url='';
6106 // var kind='image';
6107 // var albumIdx=NGY2Item.GetIdx(G, albumID);
6108 // var photoIdx=NGY2Item.GetIdx(G, photoID);
6109
6110 switch( G.O.kind ) {
6111 // MARKUP / API
6112 case '':
6113 AlbumGetMarkupOrApi(fnToCall, fnParam1, fnParam2);
6114 break;
6115 // JSON, Flickr, Picasa, ...
6116 default:
6117 jQuery.nanogallery2['data_'+G.O.kind](G, 'AlbumGetContent', albumID, fnToCall, fnParam1, fnParam2 );
6118 }
6119
6120 }
6121
6122 var mediaList = {
6123 youtube : {
6124 getID: function( url ) {
6125 // https://stackoverflow.com/questions/10591547/how-to-get-youtube-video-id-from-url
6126 var s = url.match( /(?:https?:\/{2})?(?:w{3}\.)?youtu(?:be)?\.(?:com|be)(?:\/watch\?v=|\/)([^\s&]+)/ );
6127 return s != null ? s[1] : null;
6128 },
6129 thumbUrl: function( id ) {
6130 return 'https://img.youtube.com/vi/' + id + '/hqdefault.jpg';
6131 },
6132 url: function( id ) {
6133 return 'https://www.youtube.com/embed/' + id;
6134 },
6135 markup: function( id ) {
6136 // return '<iframe class="nGY2ViewerMedia" src="https://www.youtube.com/embed/' + id + '?rel=0" frameborder="0" gesture="media" allowfullscreen></iframe>';
6137 return '<iframe class="nGY2ViewerMedia" src="https://www.youtube.com/embed/' + id + '?rel=0" frameborder="0" allow="autoplay" allowfullscreen></iframe>';
6138 },
6139 kind: 'iframe'
6140 },
6141 vimeo : {
6142 getID: function( url ) {
6143 // https://stackoverflow.com/questions/2916544/parsing-a-vimeo-id-using-javascript
6144 // var s = url.match( /^.*(vimeo\.com\/)((channels\/[A-z]+\/)|(groups\/[A-z]+\/videos\/))?([0-9]+)/ );
6145 var s = url.match( /(http|https)?:\/\/(www\.)?vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/([^\/]*)\/videos\/|)(\d+)(?:|\/\?)/ );
6146 return s != null ? s[4] : null;
6147 },
6148 url: function( id ) {
6149 return 'https://player.vimeo.com/video/' + id;
6150 },
6151 markup: function( id ) {
6152 // return '<iframe class="nGY2ViewerMedia" src="https://player.vimeo.com/video/' + id + '?rel=0" frameborder="0" gesture="media" allowfullscreen></iframe>';
6153 // return '<iframe class="nGY2ViewerMedia" src="https://player.vimeo.com/video/' + id + '?rel=0" frameborder="0" allow="autoplay; fullscreen" allowfullscreen></iframe>';
6154 return '<iframe class="nGY2ViewerMedia" src="https://player.vimeo.com/video/' + id + '" frameborder="0" allow="autoplay; fullscreen" allowfullscreen></iframe>';
6155 },
6156 kind: 'iframe'
6157 },
6158 dailymotion : {
6159 getID: function( url ) {
6160 // https://stackoverflow.com/questions/12387389/how-to-parse-dailymotion-video-url-in-javascript
6161 var m = url.match(/^.+dailymotion.com\/(video|hub)\/([^_]+)[^#]*(#video=([^_&]+))?/);
6162 if (m !== null) {
6163 if(m[4] !== undefined) {
6164 return m[4];
6165 }
6166 return m[2];
6167 }
6168 return null;
6169 },
6170 thumbUrl: function( id ) {
6171 return 'https://www.dailymotion.com/thumbnail/video/' + id;
6172 },
6173 url: function( id ) {
6174 return 'https://www.dailymotion.com/embed/video/' + id;
6175 },
6176 markup: function( id ) {
6177 // return '<iframe class="nGY2ViewerMedia" src="https://www.dailymotion.com/embed/video/' + id + '?rel=0" frameborder="0" gesture="media" allowfullscreen></iframe>';
6178 return '<iframe class="nGY2ViewerMedia" src="https://www.dailymotion.com/embed/video/' + id + '?rel=0" frameborder="0" allow="autoplay" allowfullscreen></iframe>';
6179 },
6180 kind: 'iframe'
6181 },
6182 selfhosted : {
6183 // SELF-HOSTED VIDEOS
6184 getID: function( url ) {
6185 // In order to leave things as is, I used ID to identify the extension
6186 // https://stackoverflow.com/questions/6997262/how-to-pull-url-file-extension-out-of-url-string-using-javascript
6187 // Make sure the method used for verifying the extension matches the kind of url your selfhosted video has
6188 var extension = url.split('.').pop().toLowerCase();
6189
6190 // supported extensions
6191 var s = ( extension === 'mp4' || extension === 'webm' || extension === 'ogv' || extension === '3gp' ) ? extension : null ;
6192 return s;
6193 },
6194 markup: function( url ) {
6195 // 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>';
6196 var extension = url.split('.').pop();
6197 return '<video controls class="nGY2ViewerMedia"><source src="'+ url +'" type="video/'+ extension +'" preload="auto">Your browser does not support the video tag (HTML 5).</video>';
6198 },
6199 kind: 'video',
6200 selfhosted : true
6201 }
6202 };
6203
6204 function AlbumGetMarkupOrApi ( fnToCall, fnParam1, fnParam2 ) {
6205
6206 if( G.markupOrApiProcessed === true ) {
6207 // already processed (maybe location hash to unknow reference) -> display root album
6208 DisplayAlbum('-1', 0);
6209 return;
6210 }
6211
6212 if( G.O.items !== undefined && G.O.items !== null ) {
6213 // data defined as an object in an option parameter
6214 GetContentApiObject();
6215 }
6216 else {
6217 if( G.O.$markup.length > 0 ) {
6218 // data defined as markup (href elements)
6219 GetContentMarkup( G.O.$markup );
6220 G.O.$markup=[] ;
6221 }
6222 else {
6223 NanoConsoleLog(G, 'error: no media to process.');
6224 return;
6225 }
6226 }
6227
6228 G.markupOrApiProcessed = true;
6229 if( fnToCall !== null && fnToCall !== undefined) {
6230 fnToCall( fnParam1, fnParam2, null );
6231 }
6232 }
6233
6234 function StartsWithProtocol ( path ) {
6235 if( path == undefined ) { return false; }
6236 // if( path == null ) { return false; }
6237
6238 var pattern = /^((http|https|ftp|ftps|file):\/\/)/;
6239 if( !pattern.test(path) ) {
6240 // not a full URL
6241 return false;
6242 }
6243 return true;
6244 }
6245
6246 function GetContentApiObject() {
6247 var foundAlbumID=false;
6248 var nbTitles = 0;
6249 var AlbumPostProcess = NGY2Tools.AlbumPostProcess.bind(G);
6250
6251 G.I[0].contentIsLoaded = true;
6252
6253 jQuery.each(G.O.items, function(i,item){
6254
6255 var title = '';
6256 title = GetI18nItem(item, 'title');
6257 if( title === undefined ) { title=''; }
6258
6259 var src='';
6260 if( item['src'+RetrieveCurWidth().toUpperCase()] !== undefined ) {
6261 src = item['src'+RetrieveCurWidth().toUpperCase()];
6262 }
6263 else {
6264 src = item.src;
6265 }
6266 if( !StartsWithProtocol(src) ) {
6267 src = G.O.itemsBaseURL + src;
6268 }
6269
6270 var thumbsrc = '';
6271 if( item.srct !== undefined && item.srct.length > 0 ) {
6272 thumbsrc = item.srct;
6273 if( !StartsWithProtocol(thumbsrc) ) {
6274 thumbsrc = G.O.itemsBaseURL + thumbsrc;
6275 }
6276 }
6277 else {
6278 thumbsrc = src;
6279 }
6280
6281 if( G.O.thumbnailLabel.get('title') != '' ) {
6282 title = GetImageTitle(src);
6283 }
6284
6285 var description=''; //'&nbsp;';
6286 description=GetI18nItem(item,'description');
6287 if( description === undefined ) { description=''; }
6288 //if( toType(item.description) == 'string' ) {
6289 // description=item.description;
6290 //}
6291
6292 var tags = GetI18nItem(item, 'tags');
6293 if( tags === undefined ) { tags=''; }
6294
6295 var albumID = 0;
6296 if( item.albumID !== undefined ) {
6297 albumID=item.albumID;
6298 foundAlbumID = true;
6299 }
6300 var ID = null;
6301 if( item.ID !== undefined ) {
6302 ID = item.ID;
6303 }
6304 var kind = 'image';
6305 if( item.kind !== undefined && item.kind.length > 0 ) {
6306 kind = item.kind;
6307 }
6308
6309 var newItem=NGY2Item.New( G, title, description, ID, albumID, kind, tags );
6310 if( title != '' ) {
6311 nbTitles++;
6312 }
6313
6314 // media source url - img is the default media kind
6315 newItem.setMediaURL( src, 'img');
6316
6317 // manage media kinds other than IMG
6318 jQuery.each(mediaList, function ( n, media ) {
6319 var id = media.getID(src);
6320 if( id != null ) {
6321 if( thumbsrc == src && typeof media.thumbUrl == 'function' ) {
6322 thumbsrc = media.thumbUrl(id);
6323 }
6324 if( typeof media.url == 'function' ) { src = media.url(id); }
6325 newItem.mediaKind = media.kind;
6326 newItem.mediaMarkup = ( media.selfhosted ) ? media.markup( src ) : media.markup(id);
6327 return false;
6328 }
6329 });
6330
6331 // image size
6332 if( item.imageWidth !== undefined ) { newItem.imageWidth = item.width; }
6333 if( item.imageHeight !== undefined ) { newItem.imageHeight = item.height; }
6334
6335 // THUMBNAILS
6336
6337 // thumbnail image size
6338 var tw = item.imgtWidth !== undefined ? item.imgtWidth : 0;
6339 var th = item.imgtHeight !== undefined ? item.imgtHeight : 0;
6340
6341 // default thumbnail URL and size
6342 newItem.thumbs = {
6343 url: { l1 : { xs: thumbsrc, sm: thumbsrc, me: thumbsrc, la: thumbsrc, xl: thumbsrc }, lN : { xs: thumbsrc, sm: thumbsrc, me: thumbsrc, la: thumbsrc, xl: thumbsrc } },
6344 width: { l1 : { xs: tw, sm: tw, me: tw, la: tw, xl: tw }, lN : { xs: tw, sm: tw, me: tw, la: tw, xl: tw } },
6345 height: { l1 : { xs: th, sm: th, me: th, la: th, xl: th }, lN : { xs: th, sm: th, me: th, la: th, xl: th } }
6346 };
6347
6348 // default media type -> IMG
6349 if( newItem.mediaKind == 'img' ) {
6350
6351 // responsive thumbnails URL and size
6352 var lst=['xs', 'sm', 'me', 'la', 'xl'];
6353 for( var i=0; i< lst.length; i++ ) {
6354 // url
6355 var turl = item['srct' + lst[i].toUpperCase()];
6356 if( turl !== undefined ) {
6357 if( !StartsWithProtocol(turl) ) {
6358 turl = G.O.itemsBaseURL + turl;
6359 }
6360 newItem.url.l1[lst[i]] = turl;
6361 newItem.url.lN[lst[i]] = turl;
6362 }
6363 // width
6364 var tw = item['imgt' + lst[i].toUpperCase() + 'Width'];
6365 if( tw != undefined ) {
6366 newItem.width.l1[lst[i]] = parseInt(tw);
6367 newItem.width.lN[lst[i]] = parseInt(tw);
6368 }
6369 // height
6370 var th = item['imgt' + lst[i].toUpperCase() + 'Height'];
6371 if( th != undefined ) {
6372 newItem.height.l1[lst[i]] = parseInt(th);
6373 newItem.height.lN[lst[i]] = parseInt(th);
6374 }
6375 }
6376 }
6377
6378 // dominant colors (needs to be a base64 gif)
6379 if( item.imageDominantColors !== undefined ) {
6380 newItem.imageDominantColors = item.imageDominantColors;
6381 }
6382 // dominant color (rgb hex)
6383 if( item.imageDominantColor !== undefined ) {
6384 newItem.imageDominantColor = item.imageDominantColor;
6385 }
6386
6387 // dest url
6388 if( item.destURL !== undefined && item.destURL.length>0 ) {
6389 newItem.destinationURL = item.destURL;
6390 }
6391
6392 // download image url
6393 if( item.downloadURL !== undefined && item.downloadURL.length>0 ) {
6394 newItem.downloadURL = item.downloadURL;
6395 }
6396
6397 // EXIF DATA
6398 // Exif - model
6399 if( item.exifModel !== undefined ) { newItem.exif.model = item.exifModel; }
6400 // Exif - flash
6401 if( item.exifFlash !== undefined ) { newItem.exif.flash = item.exifFlash; }
6402 // Exif - focallength
6403 if( item.exifFocalLength !== undefined ) { newItem.exif.focallength = item.exifFocalLength; }
6404 // Exif - fstop
6405 if( item.exifFStop !== undefined ) { newItem.exif.fstop = item.exifFStop; }
6406 // Exif - exposure
6407 if( item.exifExposure !== undefined ) { newItem.exif.exposure = item.exifExposure; }
6408 // Exif - time
6409 if( item.exifIso !== undefined ) { newItem.exif.iso = item.exifIso; }
6410 // Exif - iso
6411 if( item.exifTime !== undefined ) { newItem.exif.time = item.exifTime; }
6412 // Exif - location
6413 if( item.exifLocation !== undefined ) { newItem.exif.location = item.exifLocation; }
6414
6415
6416 // custom data
6417 if( item.customData !== null ) {
6418 newItem.customData = cloneJSObject( item.customData );
6419 }
6420
6421 newItem.contentIsLoaded = true;
6422
6423 var fu = G.O.fnProcessData;
6424 if( fu !== null ) {
6425 typeof fu == 'function' ? fu(newItem, 'api', item) : window[fu](newItem, 'api', item);
6426 }
6427
6428 AlbumPostProcess(albumID);
6429 });
6430
6431 // if( foundAlbumID ) { G.O.displayBreadcrumb=true; }
6432 if( nbTitles == 0 ) { G.O.thumbnailLabel.display=false; }
6433
6434 }
6435
6436
6437 // Returns the text of the DOM element (without children)
6438 // Because jQuery().text() returns the text of all children
6439 function ElementGetText( element ) {
6440
6441 var text = '';
6442 if( element.childNodes[0] !== undefined ) {
6443 if( element.childNodes[0].nodeValue !== null && element.childNodes[0].nodeValue !== undefined ) {
6444 text = element.childNodes[0].nodeValue.trim();
6445 }
6446 }
6447 return text;
6448 }
6449
6450 // Extract items from the jQuery elements
6451 function GetContentMarkup( $elements, group ) {
6452 var foundAlbumID = false;
6453 var nbTitles = 0;
6454 var AlbumPostProcess = NGY2Tools.AlbumPostProcess.bind(G);
6455 var GetImageTitleFromURL = NGY2Tools.GetImageTitleFromURL.bind(G);
6456
6457 G.I[0].contentIsLoaded = true;
6458
6459 jQuery.each($elements, function(i, item){
6460
6461 // compare to group defined on the element that has been clicked (lightbox standalone)
6462 if( item.dataset.nanogallery2Lgroup != group ) { return; }
6463
6464 // ignore element <SCRIPT>
6465 if( item.nodeName == 'SCRIPT' ) { return; }
6466
6467 // create dictionnary with all data attribute name in lowercase (to be case unsensitive)
6468 var data = {
6469 // all possible data attributes with some default values
6470 'data-ngdesc': '', // item description
6471 'data-ngid': null, // ID
6472 'data-ngkind': 'image', // kind (image, album, albumup)
6473 'data-ngtags': null, // tags
6474 'data-ngdest': '', // destination URL
6475 'data-ngthumbimgwidth': 0, // thumbnail width
6476 'data-ngthumbimgheight': 0, // thumbnail height
6477 'data-ngimagewidth': 0, // image width
6478 'data-ngimageheight': 0, // image height
6479 'data-ngimagedominantcolors': null, // image dominant colors
6480 'data-ngimagedominantcolor': null, // image dominant colors
6481 'data-ngexifmodel': '', // EXIF data
6482 'data-ngexifflash': '',
6483 'data-ngexiffocallength': '',
6484 'data-ngexiffstop': '',
6485 'data-ngexifexposure': '',
6486 'data-ngexifiso': '',
6487 'data-ngexiftime': '',
6488 'data-ngexiflocation': '',
6489 'data-ngsrc': '',
6490 'alt': ''
6491 };
6492
6493 // Extract data attributes from main item
6494 [].forEach.call( item.attributes, function(attr) {
6495 data[attr.name.toLowerCase()] = attr.value.trim();
6496 });
6497
6498 var title = ElementGetText(item);
6499 if( title == '' && data.alt != '') {
6500 // no title -> check ALT attribute of main element
6501 title = data['alt'];
6502 }
6503
6504 // Complete with data attributes from all children
6505 jQuery.each($(item).children(), function(i, sub_item){
6506
6507 // title may be set on a child element
6508 if( title == '' ) {
6509 title = ElementGetText(sub_item);
6510 }
6511
6512 [].forEach.call( sub_item.attributes, function(attr) {
6513 data[attr.name.toLowerCase()] = attr.value.trim();
6514 });
6515
6516 if( title == '' && data.alt != '') {
6517 // no title -> check ALT attribute of sub element
6518 title = data['alt'];
6519 }
6520
6521 });
6522
6523 // BIG IMAGE URL
6524 // responsive image URL
6525 var src = '',
6526 st = RetrieveCurWidth().toUpperCase();
6527 if( data.hasOwnProperty('data-ngsrc'+st) ) {
6528 src = data['data-ngsrc'+st];
6529 }
6530 // image URL from data-ngsrc attribute
6531 // if( src == '' ) {
6532 // src = data['data-ngsrc'];
6533 // }
6534 // image URL from href attribute (a element)
6535 // if( src == '' ) {
6536 // src = data['href'];
6537 // }
6538 src = src || data['data-ngsrc'] || data['href'];
6539 if( src !== undefined && !StartsWithProtocol(src) ) { // do not add the base URL if src starts with a protocol (http, https...)
6540 src = G.O.itemsBaseURL + src;
6541 }
6542
6543 // THUMBNAIL IMAGE
6544 var thumbsrc = '';
6545 // src attribute (img element)
6546 if( data.hasOwnProperty('src') ) {
6547 thumbsrc = data['src'];
6548 }
6549 // data-ngthumb attribute
6550 if( thumbsrc == '' && data.hasOwnProperty('data-ngthumb') ) {
6551 thumbsrc = data['data-ngthumb'];
6552 }
6553 if( thumbsrc == '' ) {
6554 thumbsrc = src; // no thumbnail image URL -> use big image URL
6555 }
6556 if( thumbsrc !== undefined && !StartsWithProtocol(thumbsrc) ) {
6557 thumbsrc = G.O.itemsBaseURL + thumbsrc;
6558 }
6559
6560 // ignore if no media URL at all
6561 if( src === undefined && thumbsrc === undefined ) { return; }
6562
6563 //newObj.description=jQuery(item).attr('data-ngdesc');
6564 var description = data['data-ngdesc'];
6565 var ID = data['id'] || data['data-ngid'];
6566 // if( ID == undefined ) {
6567 // ID = data['data-ngid'];
6568 // }
6569 var kind = data['data-ngkind'];
6570 var tags = data['data-ngtags'];
6571
6572 var albumID = '0';
6573 if( data.hasOwnProperty('data-ngalbumid') ) {
6574 albumID = data['data-ngalbumid'];
6575 foundAlbumID = true;
6576 }
6577
6578 // var title = jQuery(item).text();
6579 var title_from_url = GetImageTitleFromURL( src );
6580 if( title_from_url != '' ) {
6581 title = title_from_url;
6582 }
6583
6584 var newItem = NGY2Item.New( G, title, description, ID, albumID, kind, tags );
6585 if( title != '' ) {
6586 nbTitles++;
6587 }
6588
6589 // media source url - img is the default media kind
6590 newItem.setMediaURL( src, 'img');
6591
6592 // manage media kinds other than IMG
6593 // newItem.mediaKind = 'img';
6594 jQuery.each(mediaList, function ( n, media ) {
6595 var id = media.getID(src);
6596 if( id != null ) {
6597 if( thumbsrc == src && typeof media.thumbUrl == 'function' ) {
6598 thumbsrc = media.thumbUrl(id);
6599 }
6600 if( typeof media.url == 'function' ) { src = media.url(id); }
6601 newItem.mediaKind = media.kind;
6602 newItem.mediaMarkup = ( media.selfhosted ) ? media.markup( src ) : media.markup(id);
6603 return false;
6604 }
6605 });
6606
6607
6608 // Big image size
6609 newItem.imageWidth = parseInt( data['data-ngimagewidth'] );
6610 newItem.imageHeight = parseInt( data['data-ngimageheight'] );
6611
6612 // default thumbnail image URL and size
6613 var tw = parseInt(data['data-ngthumbimgwidth']);
6614 var th = parseInt(data['data-ngthumbimgheight']);
6615 newItem.thumbs = {
6616 url: { l1 : { xs: thumbsrc, sm: thumbsrc, me: thumbsrc, la: thumbsrc, xl: thumbsrc }, lN : { xs: thumbsrc, sm: thumbsrc, me: thumbsrc, la: thumbsrc, xl: thumbsrc } },
6617 width: { l1 : { xs: tw, sm: tw, me: tw, la: tw, xl: tw }, lN : { xs: tw, sm: tw, me: tw, la: tw, xl: tw } },
6618 height: { l1 : { xs: th, sm: th, me: th, la: th, xl: th }, lN : { xs: th, sm: th, me: th, la: th, xl: th } }
6619 };
6620
6621 // Media type -> IMG
6622 if( newItem.mediaKind == 'img' ) {
6623
6624 // responsive thumbnails URL and size
6625 var lst = ['xs', 'sm', 'me', 'la', 'xl'];
6626 for( var i = 0; i < lst.length; i++ ) {
6627 // url
6628 if( data.hasOwnProperty('data-ngthumb' + lst[i]) ) {
6629 var turl=data['data-ngthumb' + lst[i]];
6630 if( !StartsWithProtocol(turl) ) {
6631 turl = G.O.itemsBaseURL + turl;
6632 }
6633 newItem.url.l1[lst[i]] = turl;
6634 newItem.url.lN[lst[i]] = turl;
6635 }
6636
6637 // width
6638 if( data.hasOwnProperty('data-ngthumb' + lst[i] + 'width') ) {
6639 var tw=parseInt(data['data-ngthumb' + lst[i] + 'width']);
6640 newItem.width.l1[lst[i]] = tw;
6641 newItem.width.lN[lst[i]] = tw;
6642 }
6643 // height
6644 if( data.hasOwnProperty('data-ngthumb' + lst[i] + 'height') ) {
6645 var th=parseInt('data-ngthumb' + lst[i] + 'height');
6646 newItem.height.l1[lst[i]] = th;
6647 newItem.height.lN[lst[i]] = th;
6648 }
6649 }
6650 }
6651
6652
6653 // dominant colorS (needs to be a base64 gif)
6654 newItem.imageDominantColors = data['data-ngimagedominantcolors'];
6655 // dominant color (rgb hex)
6656 newItem.imageDominantColor = data['data-ngimagedominantcolors'];
6657
6658 newItem.destinationURL = data['data-ngdest'];
6659 newItem.downloadURL = data['data-ngdownloadurl'];
6660
6661 // Exif - model
6662 newItem.exif.model=data['data-ngexifmodel'];
6663 // Exif - flash
6664 newItem.exif.flash=data['data-ngexifflash'];
6665 // Exif - focallength
6666 newItem.exif.focallength=data['data-ngexiffocallength'];
6667 // Exif - fstop
6668 newItem.exif.fstop=data['data-ngexiffstop'];
6669 // Exif - exposure
6670 newItem.exif.exposure=data['data-ngexifexposure'];
6671 // Exif - iso
6672 newItem.exif.iso=data['data-ngexifiso'];
6673 // Exif - time
6674 newItem.exif.time=data['data-ngexiftime'];
6675 // Exif - location
6676 newItem.exif.location=data['data-ngexiflocation'];
6677
6678 newItem.contentIsLoaded = true;
6679
6680 // custom data
6681 if( jQuery(item).data('customdata') !== undefined ) {
6682 newItem.customData = cloneJSObject(jQuery(item).data('customdata'));
6683 }
6684 // custom data
6685 if( jQuery(item).data('ngcustomdata') !== undefined ) {
6686 newItem.customData = cloneJSObject(jQuery(item).data('ngcustomdata'));
6687 }
6688
6689 var fu=G.O.fnProcessData;
6690 if( fu !== null ) {
6691 typeof fu == 'function' ? fu(newItem, 'markup', item) : window[fu](newItem, 'markup', item);
6692 }
6693
6694 AlbumPostProcess(albumID);
6695
6696 });
6697
6698 // if( foundAlbumID ) { G.O.displayBreadcrumb=true; }
6699 if( nbTitles == 0 ) { G.O.thumbnailLabel.display = false; }
6700
6701 }
6702
6703
6704 // ################################
6705 // ##### DEFINE VARIABLES #####
6706 // ################################
6707
6708
6709 /** @function DefineVariables */
6710 function DefineVariables() {
6711
6712 // change 'picasa' to 'google' for compatibility reason
6713 if( G.O.kind.toUpperCase() == 'PICASA' || G.O.kind.toUpperCase() == 'GOOGLE') {
6714 G.O.kind='google2';
6715 }
6716
6717 // management of screen width
6718 G.GOM.cache.viewport = getViewport();
6719 G.GOM.curWidth = RetrieveCurWidth();
6720
6721 // tumbnail toolbar
6722 jQuery.extend(true, G.tn.toolbar.image, G.O.thumbnailToolbarImage );
6723 jQuery.extend(true, G.tn.toolbar.album, G.O.thumbnailToolbarAlbum );
6724 var t = ['image', 'album'];
6725 var pos= ['topLeft', 'topRight', 'bottomLeft', 'bottomRight']
6726 for( var i=0; i < t.length ; i++ ) {
6727 for( var j=0; j < pos.length ; j++ ) {
6728 G.tn.toolbar[t[i]][pos[j]] = G.tn.toolbar[t[i]][pos[j]].toUpperCase();
6729 }
6730 }
6731
6732 // convert label settings
6733 if( G.O.thumbnailLabel.position == 'overImageOnBottom' ) {
6734 G.O.thumbnailLabel.valign = 'bottom';
6735 G.O.thumbnailLabel.position = 'overImage';
6736 }
6737 if( G.O.thumbnailLabel.position == 'overImageOnMiddle' ) {
6738 G.O.thumbnailLabel.valign = 'middle';
6739 G.O.thumbnailLabel.position = 'overImage';
6740 }
6741 if( G.O.thumbnailLabel.position == 'overImageOnTop' ) {
6742 G.O.thumbnailLabel.valign = 'top';
6743 G.O.thumbnailLabel.position = 'overImage';
6744 }
6745 if( G.O.thumbnailL1Label !== undefined && G.O.thumbnailL1Label.position !== undefined ) {
6746 if( G.O.thumbnailL1Label.position == 'overImageOnBottom' ) {
6747 G.O.thumbnailL1Label.valign = 'bottom';
6748 G.O.thumbnailL1Label.position = 'overImage';
6749 }
6750 if( G.O.thumbnailL1Label.position == 'overImageOnMiddle' ) {
6751 G.O.thumbnailL1Label.valign = 'middle';
6752 G.O.thumbnailL1Label.position = 'overImage';
6753 }
6754 if( G.O.thumbnailL1Label.position == 'overImageOnTop' ) {
6755 G.O.thumbnailL1Label.valign = 'top';
6756 G.O.thumbnailL1Label.position = 'overImage';
6757 }
6758 }
6759
6760 // thumbnails label - level dependant settings
6761 G.O.thumbnailLabel.get = function( opt ) {
6762 if( G.GOM.curNavLevel == 'l1' && G.O.thumbnailL1Label !== undefined && G.O.thumbnailL1Label[opt] !== undefined ) {
6763 return G.O.thumbnailL1Label[opt];
6764 }
6765 else {
6766 return G.O.thumbnailLabel[opt];
6767 }
6768 };
6769 G.O.thumbnailLabel.set = function( opt, value ) {
6770 if( G.GOM.curNavLevel == 'l1' && G.O.thumbnailL1Label !== undefined && G.O.thumbnailL1Label[opt] !== undefined ) {
6771 G.O.thumbnailL1Label[opt]=value;
6772 }
6773 else {
6774 G.O.thumbnailLabel[opt]=value;
6775 }
6776 };
6777
6778 if( G.O.blockList != '' ) { G.blockList = G.O.blockList.toUpperCase().split('|'); }
6779 if( G.O.allowList != '' ) { G.allowList = G.O.allowList.toUpperCase().split('|'); }
6780
6781 if( G.O.albumList2 !== undefined && G.O.albumList2 !== null && G.O.albumList2.constructor === Array ) {
6782 var l=G.O.albumList2.length;
6783 for(var i=0; i< l; i++ ) {
6784 G.albumList.push(G.O.albumList2[i]);
6785 }
6786 // G.albumList=G.O.albumList.toUpperCase().split('|');
6787 }
6788 if( G.O.albumList2 !== undefined && typeof G.O.albumList2 == 'string' ) {
6789 G.albumList.push(G.O.albumList2);
6790 }
6791
6792
6793 // thumbnail image crop
6794 G.tn.opt.lN.crop = G.O.thumbnailCrop;
6795 G.tn.opt.l1.crop = G.O.thumbnailL1Crop != null ? G.O.thumbnailL1Crop : G.O.thumbnailCrop;
6796
6797
6798 function ThumbnailOpt( lN, l1, opt) {
6799 G.tn.opt.lN[opt] = G.O[lN];
6800 G.tn.opt.l1[opt] = G.O[lN];
6801 if( toType(G.O[l1]) == 'number' ) {
6802 G.tn.opt.l1[opt] = G.O[l1];
6803 }
6804 }
6805 // thumbnail stacks
6806 ThumbnailOpt('thumbnailStacks', 'thumbnailL1Stacks', 'stacks');
6807 // thumbnail stacks translate X
6808 ThumbnailOpt('thumbnailStacksTranslateX', 'thumbnailL1StacksTranslateX', 'stacksTranslateX');
6809 // thumbnail stacks translate Y
6810 ThumbnailOpt('thumbnailStacksTranslateY', 'thumbnailL1StacksTranslateY', 'stacksTranslateY');
6811 // thumbnail stacks translate Z
6812 ThumbnailOpt('thumbnailStacksTranslateZ', 'thumbnailL1StacksTranslateZ', 'stacksTranslateZ');
6813 // thumbnail stacks rotate X
6814 ThumbnailOpt('thumbnailStacksRotateX', 'thumbnailL1StacksRotateX', 'stacksRotateX');
6815 // thumbnail stacks rotate Y
6816 ThumbnailOpt('thumbnailStacksRotateY', 'thumbnailL1StacksRotateY', 'stacksRotateY');
6817 // thumbnail stacks rotate Z
6818 ThumbnailOpt('thumbnailStacksRotateZ', 'thumbnailL1StacksRotateZ', 'stacksRotateZ');
6819 // thumbnail stacks scale
6820 ThumbnailOpt('thumbnailStacksScale', 'thumbnailL1StacksScale', 'stacksScale');
6821 // thumbnail gutter width
6822 // ThumbnailOpt('thumbnailGutterWidth', 'thumbnailL1GutterWidth', 'gutterWidth');
6823 // thumbnail gutter height
6824 // ThumbnailOpt('thumbnailGutterHeight', 'thumbnailL1GutterHeight', 'gutterHeight');
6825 // thumbnail border horizontal
6826 ThumbnailOpt('thumbnailBorderHorizontal', 'thumbnailL1BorderHorizontal', 'borderHorizontal');
6827 // thumbnail border vertical
6828 ThumbnailOpt('thumbnailBorderVertical', 'thumbnailL1BorderVertical', 'borderVertical');
6829 // thumbnail grid base height (for cascading layout)
6830 ThumbnailOpt('thumbnailBaseGridHeight', 'thumbnailL1BaseGridHeight', 'baseGridHeight');
6831
6832
6833 // Set same value to all widths
6834 function ResponsiveSetSize( setting, level, v ) {
6835 G.tn.settings[setting][level]['xs'] = v;
6836 G.tn.settings[setting][level]['sm'] = v;
6837 G.tn.settings[setting][level]['me'] = v;
6838 G.tn.settings[setting][level]['la'] = v;
6839 G.tn.settings[setting][level]['xl'] = v;
6840 }
6841
6842 // Get and evaluate responsive values from one option
6843 // Responsive is with syntax: n XSn1 SMn2 MEn3 LAn4 XLn5 (where n is the default value)
6844 // Value 'auto' is accepted for all options, but is handeld only for thumbnail width/height
6845 function ResponsiveOption( option, setting, level ) {
6846 var v = G.O[option];
6847
6848 if( v === undefined || v === null ) { return; }
6849
6850 // if( toType(v) == 'number' ) {
6851 if( toType(v) == 'number' || v.indexOf(' ') == -1 ) {
6852 // set value for all widths
6853 var vn = 'auto';
6854 if( v != 'auto' ) { vn = parseInt(v); }
6855 ResponsiveSetSize( setting, level, vn );
6856 }
6857 else {
6858 var sp = v.split(' ');
6859 if( sp.length > 0 && +sp[0] === +sp[0] ) { // check if sp[0] is a number
6860 // first value is the default size for all widths
6861 var vn = 'auto';
6862 if( sp[0] != 'auto' ) { vn = parseInt(sp[0]); }
6863 ResponsiveSetSize( setting, level, vn );
6864 }
6865 for( var i = 1; i < sp.length; i++ ) {
6866 if( /^xs|sm|me|la|xl/i.test( sp[i] ) ) { // regex: i ignores the case and ^ means "starts with"
6867 var wi = sp[i].substring(0, 2).toLowerCase();
6868 var va = sp[i].substring(2);
6869 var vn = 'auto';
6870 if( va != 'auto' ) { vn = parseInt(va); }
6871 G.tn.settings[setting][level][wi] = vn;
6872 }
6873 }
6874 }
6875 }
6876
6877 ResponsiveOption('thumbnailGutterWidth', 'gutterWidth', 'lN');
6878 ResponsiveOption('thumbnailGutterWidth', 'gutterWidth', 'l1'); // set default values for first level
6879 ResponsiveOption('thumbnailL1GutterWidth', 'gutterWidth', 'l1');
6880 ResponsiveOption('thumbnailGutterHeight', 'gutterHeight', 'lN');
6881 ResponsiveOption('thumbnailGutterHeight', 'gutterHeight', 'l1'); // set default values for first level
6882 ResponsiveOption('thumbnailL1GutterHeight', 'gutterHeight', 'l1');
6883
6884 // gallery display mode
6885 G.galleryDisplayMode.lN = G.O.galleryDisplayMode.toUpperCase();
6886 G.galleryDisplayMode.l1 = G.O.galleryL1DisplayMode != null ? G.O.galleryL1DisplayMode.toUpperCase() : G.O.galleryDisplayMode.toUpperCase();
6887
6888 // gallery maximum number of lines of thumbnails
6889 G.galleryMaxRows.lN = G.O.galleryMaxRows;
6890 G.galleryMaxRows.l1 = toType(G.O.galleryL1MaxRows) == 'number' ? G.O.galleryL1MaxRows : G.O.galleryMaxRows;
6891
6892 // gallery last row full
6893 G.galleryLastRowFull.lN = G.O.galleryLastRowFull;
6894 G.galleryLastRowFull.l1 = G.O.galleryL1LastRowFull != null ? G.O.galleryL1LastRowFull : G.O.galleryLastRowFull;
6895
6896 // gallery sorting
6897 G.gallerySorting.lN = G.O.gallerySorting.toUpperCase();
6898 G.gallerySorting.l1 = G.O.galleryL1Sorting != null ? G.O.galleryL1Sorting.toUpperCase() : G.gallerySorting.lN;
6899
6900 // gallery display transition
6901 G.galleryDisplayTransition.lN = G.O.galleryDisplayTransition.toUpperCase();
6902 G.galleryDisplayTransition.l1 = G.O.galleryL1DisplayTransition != null ? G.O.galleryL1DisplayTransition.toUpperCase() : G.galleryDisplayTransition.lN;
6903
6904 // gallery display transition duration
6905 G.galleryDisplayTransitionDuration.lN = G.O.galleryDisplayTransitionDuration;
6906 G.galleryDisplayTransitionDuration.l1 = G.O.galleryL1DisplayTransitionDuration != null ? G.O.galleryL1DisplayTransitionDuration : G.galleryDisplayTransitionDuration.lN;
6907
6908 // gallery max items per album (not for inline/api defined items)
6909 G.galleryMaxItems.lN = G.O.galleryMaxItems;
6910 G.galleryMaxItems.l1 = toType(G.O.galleryL1MaxItems) == 'number' ? G.O.galleryL1MaxItems : G.O.galleryMaxItems;
6911
6912 // gallery filter tags
6913 G.galleryFilterTags.lN = G.O.galleryFilterTags;
6914 G.galleryFilterTags.l1 = G.O.galleryL1FilterTags != null ? G.O.galleryL1FilterTags : G.O.galleryFilterTags;
6915
6916 // gallery filter tags mode
6917 G.galleryFilterTagsMode.lN = G.O.galleryFilterTagsMode;
6918 G.galleryFilterTagsMode.l1 = G.O.galleryL1FilterTagsMode != null ? G.O.galleryL1FilterTagsMode : G.O.galleryFilterTagsMode;
6919
6920 // gallery pagination
6921 G.O.galleryPaginationMode = G.O.galleryPaginationMode.toUpperCase();
6922
6923 if( toType(G.O.slideshowDelay) == 'number' && G.O.slideshowDelay >= 2000 ) {
6924 G.VOM.slideshowDelay = G.O.slideshowDelay;
6925 }
6926 else {
6927 NanoConsoleLog(G, 'Parameter "slideshowDelay" must be an integer >= 2000 ms.');
6928 }
6929
6930 // gallery display transition
6931 if( typeof G.O.thumbnailDisplayTransition == 'boolean' ) {
6932 if( G.O.thumbnailDisplayTransition === true ) {
6933 G.tn.opt.lN.displayTransition = 'FADEIN';
6934 G.tn.opt.l1.displayTransition = 'FADEIN';
6935 }
6936 else {
6937 G.tn.opt.lN.displayTransition = 'NONE';
6938 G.tn.opt.l1.displayTransition = 'NONE';
6939 }
6940 }
6941
6942 if( G.O.fnThumbnailDisplayEffect !== '' ) {
6943 G.tn.opt.lN.displayTransition = 'CUSTOM';
6944 G.tn.opt.l1.displayTransition = 'CUSTOM';
6945 }
6946 if( G.O.fnThumbnailL1DisplayEffect !== '' ) {
6947 G.tn.opt.l1.displayTransition = 'CUSTOM';
6948 }
6949
6950
6951 // thumbnail display transition easing
6952 // set default easing
6953 ThumbnailOpt('thumbnailDisplayTransitionEasing', 'thumbnailL1DisplayTransitionEasing', 'displayTransitionEasing');
6954 // parse thumbnail display transition
6955 function thumbnailDisplayTransitionParse( cfg, level ) {
6956 if( typeof cfg == 'string' ) {
6957 var st=cfg.split('_');
6958 if( st.length == 1 ) {
6959 G.tn.opt[level]['displayTransition'] = cfg.toUpperCase();
6960 }
6961 if( st.length == 2 ) {
6962 G.tn.opt[level]['displayTransition'] = st[0].toUpperCase();
6963 G.tn.opt[level]['displayTransitionStartVal'] = Number(st[1]);
6964 }
6965 if( st.length == 3 ) {
6966 G.tn.opt[level]['displayTransition'] = st[0].toUpperCase();
6967 G.tn.opt[level]['displayTransitionStartVal'] = Number(st[1]);
6968 G.tn.opt[level]['displayTransitionEasing'] = st[2];
6969 }
6970 }
6971 }
6972 thumbnailDisplayTransitionParse( G.O.thumbnailDisplayTransition, 'lN');
6973 thumbnailDisplayTransitionParse( G.O.thumbnailDisplayTransition, 'l1');
6974 thumbnailDisplayTransitionParse( G.O.thumbnailL1DisplayTransition, 'l1');
6975
6976
6977 // thumbnail display transition duration
6978 ThumbnailOpt('thumbnailDisplayTransitionDuration', 'thumbnailL1DisplayTransitionDuration', 'displayTransitionDuration');
6979 // thumbnail display transition interval duration
6980 ThumbnailOpt('thumbnailDisplayInterval', 'thumbnailL1DisplayInterval', 'displayInterval');
6981 // thumbnail display order
6982 ThumbnailOpt('thumbnailDisplayOrder', 'thumbnailL1DisplayOrder', 'displayOrder');
6983
6984
6985 // resolution breakpoints --> convert old syntax to new one
6986 if( G.O.thumbnailSizeSM !== undefined ) { G.O.breakpointSizeSM = G.O.thumbnailSizeSM; }
6987 if( G.O.thumbnailSizeME !== undefined ) { G.O.breakpointSizeME = G.O.thumbnailSizeME; }
6988 if( G.O.thumbnailSizeLA !== undefined ) { G.O.breakpointSizeLA = G.O.thumbnailSizeLA; }
6989 if( G.O.thumbnailSizeXL !== undefined ) { G.O.breakpointSizeXL = G.O.thumbnailSizeXL; }
6990
6991 // THUMBNAIL BUILD INIT
6992 //level 1
6993 if( G.O.thumbnailL1BuildInit2 !== undefined ) {
6994 var t1 = G.O.thumbnailL1BuildInit2.split('|');
6995 for( var i = 0; i < t1.length; i++ ) {
6996 var o1 = t1[i].trim().split('_');
6997 if( o1.length == 3 ) {
6998 var i1 = NewTBuildInit();
6999 i1.element = ThumbnailOverEffectsGetCSSElement(o1[0], '');
7000 i1.property = o1[1];
7001 i1.value = o1[2];
7002 G.tn.buildInit.level1.push(i1);
7003 }
7004 }
7005 }
7006 //level N
7007 if( G.O.thumbnailBuildInit2 !== undefined ) {
7008 var t1 = G.O.thumbnailBuildInit2.split('|');
7009 for( var i = 0; i < t1.length; i++ ) {
7010 var o1 = t1[i].trim().split('_');
7011 if( o1.length == 3 ) {
7012 var i1 = NewTBuildInit();
7013 i1.element = ThumbnailOverEffectsGetCSSElement(o1[0], '');
7014 i1.property = o1[1];
7015 i1.value = o1[2];
7016 G.tn.buildInit.std.push(i1);
7017 }
7018 }
7019 }
7020
7021
7022 // THUMBNAIL HOVER EFFETCS
7023
7024 // thumbnails hover effects - Level1
7025 var tL1HE = G.O.thumbnailL1HoverEffect2;
7026 if( tL1HE !== undefined ) {
7027 switch( toType(tL1HE) ) {
7028 case 'string': {
7029 let tmp = tL1HE.split('|');
7030 for(var i = 0; i < tmp.length; i++) {
7031 let oDef = NewTHoverEffect();
7032 oDef = ThumbnailHoverEffectExtract( tmp[i].trim(), oDef );
7033 if( oDef != null ) {
7034 G.tn.hoverEffects.level1.push(oDef);
7035 }
7036 }
7037 break;
7038 }
7039 case 'object': {
7040 let oDef = NewTHoverEffect();
7041 oDef = jQuery.extend(oDef,tL1HE);
7042 oDef = ThumbnailHoverEffectExtract( oDef.name, oDef );
7043 if( oDef != null ) {
7044 G.tn.hoverEffects.level1.push(oDef);
7045 }
7046 break;
7047 }
7048 case 'array': {
7049 for(var i = 0; i < tL1HE.length; i++) {
7050 let oDef = NewTHoverEffect();
7051 oDef = jQuery.extend(oDef,tL1HE[i]);
7052 oDef = ThumbnailHoverEffectExtract( oDef.name, oDef );
7053 if( oDef != null ) {
7054 G.tn.hoverEffects.level1.push(oDef);
7055 }
7056 }
7057 break;
7058 }
7059 case 'null':
7060 break;
7061 default:
7062 NanoAlert(G, 'incorrect parameter for "thumbnailL1HoverEffect2".');
7063 }
7064 }
7065 G.tn.hoverEffects.level1 = ThumbnailOverEffectsPreset(G.tn.hoverEffects.level1);
7066
7067 // thumbnails hover effects - other levels
7068 var tHE = G.O.thumbnailHoverEffect2;
7069 switch( toType(tHE) ) {
7070 case 'string': {
7071 let tmp = tHE.split('|');
7072 for(var i = 0; i < tmp.length; i++) {
7073 let oDef = NewTHoverEffect();
7074 oDef = ThumbnailHoverEffectExtract( tmp[i].trim(), oDef );
7075 if( oDef != null ) {
7076 G.tn.hoverEffects.std.push(oDef);
7077 }
7078 }
7079 break;
7080 }
7081 case 'object': {
7082 let oDef = NewTHoverEffect();
7083 oDef = jQuery.extend(oDef, tHE);
7084 oDef = ThumbnailHoverEffectExtract( oDef.name, oDef );
7085 if( oDef != null ) {
7086 G.tn.hoverEffects.std.push(oDef);
7087 }
7088 break;
7089 }
7090 case 'array': {
7091 for(var i = 0; i < tHE.length; i++) {
7092 let oDef = NewTHoverEffect();
7093 oDef = jQuery.extend(oDef,tHE[i]);
7094 oDef = ThumbnailHoverEffectExtract( oDef.name, oDef );
7095 if( oDef!= null ) {
7096 G.tn.hoverEffects.std.push(oDef);
7097 }
7098 }
7099 break;
7100 }
7101 case 'null':
7102 break;
7103 default:
7104 NanoAlert(G, 'incorrect parameter for "thumbnailHoverEffect2".');
7105 }
7106 G.tn.hoverEffects.std = ThumbnailOverEffectsPreset(G.tn.hoverEffects.std);
7107
7108
7109 if( G.O.touchAnimationL1 == undefined ) {
7110 G.O.touchAnimationL1 = G.O.touchAnimation;
7111 }
7112
7113 // disable thumbnail touch animation when no hover effect defined
7114 if( G.tn.hoverEffects.std.length == 0 ) {
7115 if( G.tn.hoverEffects.level1.length == 0 ) {
7116 G.O.touchAnimationL1 = false;
7117 }
7118 G.O.touchAnimation = false;
7119 }
7120
7121
7122 // thumbnail sizes
7123 if( G.O.thumbnailHeight == 0 || G.O.thumbnailHeight == '' ) { G.O.thumbnailHeight = 'auto'; }
7124 if( G.O.thumbnailWidth == 0 || G.O.thumbnailWidth == '' ) { G.O.thumbnailWidth = 'auto'; }
7125 if( G.O.thumbnailL1Height == 0 || G.O.thumbnailL1Height == '' ) { G.O.thumbnailL1Height = 'auto'; }
7126 if( G.O.thumbnailL1Width == 0 || G.O.thumbnailL1Width == '' ) { G.O.thumbnailL1Width = 'auto'; }
7127
7128 // RETRIEVE ALL THUMBNAIL SIZES
7129 // ThumbnailSizes( 'thumbnailWidth', false, 'width');
7130 // ThumbnailSizes( 'thumbnailL1Width', true, 'width');
7131 // ThumbnailSizes( 'thumbnailHeight', false, 'height');
7132 // ThumbnailSizes( 'thumbnailL1Height', true, 'height');
7133 ResponsiveOption('thumbnailWidth', 'width', 'lN');
7134 ResponsiveOption('thumbnailWidth', 'width', 'l1');
7135 ResponsiveOption('thumbnailL1Width', 'width', 'l1');
7136 ResponsiveOption('thumbnailHeight', 'height', 'lN');
7137 ResponsiveOption('thumbnailHeight', 'height', 'l1');
7138 ResponsiveOption('thumbnailL1Height', 'height', 'l1');
7139
7140
7141 G.O.thumbnailLabelHeight = parseInt(G.O.thumbnailLabelHeight);
7142
7143
7144 // retrieve all mosaic layout patterns
7145 // default pattern
7146 if( G.O.galleryMosaic != undefined ) {
7147 // clone object
7148 G.tn.settings.mosaic.l1.xs = JSON.parse(JSON.stringify(G.O.galleryMosaic));
7149 G.tn.settings.mosaic.l1.sm = JSON.parse(JSON.stringify(G.O.galleryMosaic));
7150 G.tn.settings.mosaic.l1.me = JSON.parse(JSON.stringify(G.O.galleryMosaic));
7151 G.tn.settings.mosaic.l1.la = JSON.parse(JSON.stringify(G.O.galleryMosaic));
7152 G.tn.settings.mosaic.l1.xl = JSON.parse(JSON.stringify(G.O.galleryMosaic));
7153 G.tn.settings.mosaic.lN.xs = JSON.parse(JSON.stringify(G.O.galleryMosaic));
7154 G.tn.settings.mosaic.lN.sm = JSON.parse(JSON.stringify(G.O.galleryMosaic));
7155 G.tn.settings.mosaic.lN.me = JSON.parse(JSON.stringify(G.O.galleryMosaic));
7156 G.tn.settings.mosaic.lN.la = JSON.parse(JSON.stringify(G.O.galleryMosaic));
7157 G.tn.settings.mosaic.lN.xl = JSON.parse(JSON.stringify(G.O.galleryMosaic));
7158 G.tn.settings.mosaicCalcFactor('l1', 'xs');
7159 G.tn.settings.mosaicCalcFactor('l1', 'sm');
7160 G.tn.settings.mosaicCalcFactor('l1', 'me');
7161 G.tn.settings.mosaicCalcFactor('l1', 'la');
7162 G.tn.settings.mosaicCalcFactor('l1', 'xl');
7163 G.tn.settings.mosaicCalcFactor('lN', 'xs');
7164 G.tn.settings.mosaicCalcFactor('lN', 'sm');
7165 G.tn.settings.mosaicCalcFactor('lN', 'me');
7166 G.tn.settings.mosaicCalcFactor('lN', 'la');
7167 G.tn.settings.mosaicCalcFactor('lN', 'xl');
7168 }
7169 if( G.O.galleryL1Mosaic != undefined ) {
7170 // default L1 pattern
7171 G.tn.settings.mosaic.l1.xs = JSON.parse(JSON.stringify(G.O.galleryL1Mosaic));
7172 G.tn.settings.mosaic.l1.sm = JSON.parse(JSON.stringify(G.O.galleryL1Mosaic));
7173 G.tn.settings.mosaic.l1.me = JSON.parse(JSON.stringify(G.O.galleryL1Mosaic));
7174 G.tn.settings.mosaic.l1.la = JSON.parse(JSON.stringify(G.O.galleryL1Mosaic));
7175 G.tn.settings.mosaic.l1.xl = JSON.parse(JSON.stringify(G.O.galleryL1Mosaic));
7176 G.tn.settings.mosaicCalcFactor('l1', 'xs');
7177 G.tn.settings.mosaicCalcFactor('l1', 'sm');
7178 G.tn.settings.mosaicCalcFactor('l1', 'me');
7179 G.tn.settings.mosaicCalcFactor('l1', 'la');
7180 G.tn.settings.mosaicCalcFactor('l1', 'xl');
7181 }
7182
7183 var lst=['xs','sm','me','la','xl'];
7184 // retrieve responsive mosaic definition for levels l1 & lN
7185 for( var w = 0; w < lst.length; w++ ) {
7186 if( G.O['galleryMosaic' + lst[w].toUpperCase()] != undefined ) {
7187 G.tn.settings.mosaic.lN[lst[w]] = JSON.parse(JSON.stringify( G.O['galleryMosaic' + lst[w].toUpperCase()] ));
7188 G.tn.settings.mosaic.l1[lst[w]] = JSON.parse(JSON.stringify( G.O['galleryMosaic' + lst[w].toUpperCase()] ));
7189 G.tn.settings.mosaicCalcFactor('lN',lst[w]);
7190 G.tn.settings.mosaicCalcFactor('l1', lst[w]);
7191 }
7192 }
7193 // retrieve responsive mosaic definition for level l1
7194 for( var w = 0; w < lst.length; w++ ) {
7195 if( G.O['galleryL1Mosaic' + lst[w].toUpperCase()] != undefined ) {
7196 G.tn.settings.mosaic.l1[lst[w]] = JSON.parse(JSON.stringify( G.O['galleryL1Mosaic' + lst[w].toUpperCase()] ));
7197 G.tn.settings.mosaicCalcFactor('l1', lst[w]);
7198 }
7199 }
7200
7201 G.O.imageTransition = G.O.imageTransition.toUpperCase();
7202
7203 G.layout.SetEngine();
7204
7205 // init plugins
7206 switch( G.O.kind ) {
7207 // MARKUP / API
7208 case '':
7209 break;
7210 // JSON, Flickr, Picasa, ...
7211 default:
7212 jQuery.nanogallery2['data_' + G.O.kind](G, 'Init' );
7213 }
7214
7215 }
7216
7217 // HOVER EFFECTS
7218 function ThumbnailHoverEffectExtract( name, effect) {
7219 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'];
7220
7221 var sp = name.split('_');
7222 if( sp.length >= 4 ) {
7223 // var oDef=NewTHoverEffect();
7224 effect.name = '';
7225 effect.type = sp[1];
7226 effect.from = sp[2];
7227 effect.to = sp[3];
7228 if( sp.length >= 5 ) {
7229 // effect.duration=sp[4];
7230
7231 for( var n = 4; n < sp.length; n++ ) {
7232 var v = sp[n];
7233
7234 // check if an easing name
7235 var foundEasing = false;
7236 for( var e = 0; e < easings.length; e++) {
7237 if( v == easings[e] ) {
7238 foundEasing = true;
7239 effect.easing = v;
7240 break;
7241 }
7242 }
7243 if( foundEasing === true ) {
7244 continue;
7245 }
7246
7247 v = v.toUpperCase();
7248
7249 if( v == 'HOVERIN' ) {
7250 effect.hoverout = false;
7251 continue;
7252 }
7253 if( v == 'HOVEROUT' ) {
7254 effect.hoverin = false;
7255 continue;
7256 }
7257
7258 if( v == 'KEYFRAME' ) {
7259 effect.firstKeyframe = false;
7260 continue;
7261 }
7262
7263 var num = parseInt(v.replace(/[^0-9\.]/g, ''), 10); // extract a number if one exists
7264
7265 if( num > 0 ) {
7266 // the string contains a numbers > 0
7267 if( v.indexOf('DURATION') >= 0 ) {
7268 effect.duration = num;
7269 continue;
7270 }
7271 if( v.indexOf('DURATIONBACK') >= 0 ) {
7272 effect.durationBack = num;
7273 continue;
7274 }
7275 if( v.indexOf('DELAY') >= 0 ) {
7276 effect.delay = num;
7277 continue;
7278 }
7279 if( v.indexOf('DELAYBACK') >= 0 ) {
7280 effect.delayBack = num;
7281 continue;
7282 }
7283
7284 // no parameter name found -> default is duration
7285 effect.duration = num;
7286 }
7287 }
7288 }
7289 effect.element = ThumbnailOverEffectsGetCSSElement(sp[0], effect.type);
7290
7291 }
7292 else {
7293 effect.name = name;
7294 // NanoAlert(G, 'incorrect parameter for "thumbnailHoverEffect": ' + name);
7295 // return null;
7296 }
7297 return effect;
7298 }
7299
7300
7301 function ThumbnailOverEffectsGetCSSElement( element, property ) {
7302
7303 var elts = {
7304 'image': '.nGY2GThumbnailImage',
7305 'thumbnail': '.nGY2GThumbnail',
7306 'label': '.nGY2GThumbnailLabel',
7307 'title': '.nGY2GThumbnailTitle',
7308 'description': '.nGY2GThumbnailDescription',
7309 'tools': '.nGY2GThumbnailIcons',
7310 'customlayer': '.nGY2GThumbnailCustomLayer',
7311 'default': 'nGY2GThumbnailImage'
7312 };
7313 return (elts[element] || elts['default']);
7314
7315
7316 }
7317
7318 // convert preset hover effects (nanoGALLERY) to new ones (nanogallery2)
7319 function ThumbnailOverEffectsPreset( effects ) {
7320
7321 // COMPATIBILITY WITH nanoGALLERY
7322 // OK:
7323 // 'borderLighter', 'borderDarker', 'scale120', 'labelAppear', 'labelAppear75', 'labelOpacity50', 'scaleLabelOverImage'
7324 // 'overScale', 'overScaleOutside', 'descriptionAppear'
7325 // 'slideUp', 'slideDown', 'slideRight', 'slideLeft'
7326 // 'imageScale150', 'imageScaleIn80', 'imageScale150Outside', 'imageSlideUp', 'imageSlideDown', 'imageSlideRight', 'imageSlideLeft'
7327 // 'labelSlideUpTop', 'labelSlideUp', 'labelSlideDown', 'descriptionSlideUp'
7328 // KO:
7329 // 'labelSplit4', 'labelSplitVert', 'labelAppearSplit4', 'labelAppearSplitVert'
7330 // TODO:
7331 // 'rotateCornerBL', 'rotateCornerBR', 'imageSplit4', 'imageSplitVert', 'imageRotateCornerBL', 'imageRotateCornerBR', 'imageFlipHorizontal', 'imageFlipVertical'
7332
7333
7334
7335 var newEffects=[];
7336 for( var i=0; i< effects.length; i++ ) {
7337 switch( effects[i].name.toUpperCase() ) {
7338 case 'BORDERLIGHTER': {
7339 let rgb = ColorHelperToRGB(GalleryThemeGetCurrent().thumbnail.borderColor);
7340 let name = 'thumbnail_borderColor_'+rgb+'_'+ShadeBlendConvert(0.5, rgb );
7341 newEffects.push(ThumbnailHoverEffectExtract(name, effects[i]));
7342 break;
7343 }
7344 case 'BORDERDARKER': {
7345 let rgb = ColorHelperToRGB(GalleryThemeGetCurrent().thumbnail.borderColor);
7346 let name = 'thumbnail_borderColor_'+rgb+'_'+ShadeBlendConvert(-0.5, rgb );
7347 newEffects.push(ThumbnailHoverEffectExtract(name, effects[i]));
7348 break;
7349 }
7350 case 'SCALE120':
7351 newEffects.push(ThumbnailHoverEffectExtract('thumbnail_scale_1.00_1.20', effects[i]));
7352 break;
7353 case 'LABELAPPEAR':
7354 case 'LABELAPPEAR75':
7355 newEffects.push(ThumbnailHoverEffectExtract('label_opacity_0.00_1.00', effects[i]));
7356 break;
7357 case 'TOOLSAPPEAR':
7358 newEffects.push(ThumbnailHoverEffectExtract('tools_opacity_0_1', effects[i]));
7359 break;
7360 case 'TOOLSSLIDEDOWN':
7361 newEffects.push(ThumbnailHoverEffectExtract('tools_translateY_-100%_0%', effects[i]));
7362 break;
7363 case 'TOOLSSLIDEUP':
7364 newEffects.push(ThumbnailHoverEffectExtract('tools_translateY_100%_0%', effects[i]));
7365 break;
7366 case 'LABELOPACITY50':
7367 newEffects.push(ThumbnailHoverEffectExtract('label_opacity_1.00_0.50', effects[i]));
7368 break;
7369 case 'LABELSLIDEUPTOP':
7370 case 'LABELSLIDEUP':
7371 newEffects.push(ThumbnailHoverEffectExtract('label_translateY_100%_0%', effects[i]));
7372 newEffects.push(ThumbnailHoverEffectExtract('label_translateY_100%_0%', effects[i]));
7373 break;
7374 case 'LABELSLIDEDOWN':
7375 newEffects.push(ThumbnailHoverEffectExtract('label_translateY_-100%_0%', effects[i]));
7376 break;
7377 case 'SCALELABELOVERIMAGE':
7378 newEffects.push(ThumbnailHoverEffectExtract('label_scale_0.00_1.00', effects[i]));
7379 var n = cloneJSObject(effects[i]);
7380 newEffects.push(ThumbnailHoverEffectExtract('image_scale_1.00_0.00', n));
7381 break;
7382 case 'OVERSCALE':
7383 case 'OVERSCALEOUTSIDE':
7384 //var name = 'label_scale_0_100';
7385 newEffects.push(ThumbnailHoverEffectExtract('label_scale_2.00_1.00', effects[i]));
7386 var n = cloneJSObject(effects[i]);
7387 newEffects.push(ThumbnailHoverEffectExtract('label_opacity_0.00_1.00', n));
7388 n = cloneJSObject(effects[i]);
7389 newEffects.push(ThumbnailHoverEffectExtract('image_scale_1.00_0.00', n));
7390 n = cloneJSObject(effects[i]);
7391 newEffects.push(ThumbnailHoverEffectExtract('image_opacity_1.00_0.00', n));
7392 break;
7393 case 'DESCRIPTIONAPPEAR':
7394 newEffects.push(ThumbnailHoverEffectExtract('description_opacity_0_1', effects[i]));
7395 break;
7396 case 'SLIDERIGHT':
7397 newEffects.push(ThumbnailHoverEffectExtract('image_translateX_0%_100%', effects[i]));
7398 newEffects.push(ThumbnailHoverEffectExtract('label_translateX_-100%_0%', cloneJSObject(effects[i])));
7399 break;
7400 case 'SLIDELEFT':
7401 newEffects.push(ThumbnailHoverEffectExtract('image_translateX_0%_-100%', effects[i]));
7402 newEffects.push(ThumbnailHoverEffectExtract('label_translateX_100%_0%', cloneJSObject(effects[i])));
7403 break;
7404 case 'SLIDEUP':
7405 newEffects.push(ThumbnailHoverEffectExtract('image_translateY_0%_-100%', effects[i]));
7406 newEffects.push(ThumbnailHoverEffectExtract('label_translateY_100%_0%', cloneJSObject(effects[i])));
7407 break;
7408 case 'SLIDEDOWN':
7409 newEffects.push(ThumbnailHoverEffectExtract('image_translateY_0%_100%', effects[i]));
7410 newEffects.push(ThumbnailHoverEffectExtract('label_translateY_-100%_0%', cloneJSObject(effects[i])));
7411 break;
7412 case 'IMAGESCALE150':
7413 case 'IMAGESCALE150OUTSIDE':
7414 newEffects.push(ThumbnailHoverEffectExtract('image_scale_1.00_1.50', effects[i]));
7415 break;
7416 case 'IMAGESCALEIN80':
7417 newEffects.push(ThumbnailHoverEffectExtract('image_scale_1.20_1.00', effects[i]));
7418 break;
7419 case 'IMAGESLIDERIGHT':
7420 newEffects.push(ThumbnailHoverEffectExtract('image_translateX_0%_100%', effects[i]));
7421 break;
7422 case 'IMAGESLIDELEFT':
7423 newEffects.push(ThumbnailHoverEffectExtract('image_translateX_0%_-100%', effects[i]));
7424 break;
7425 case 'IMAGESLIDEUP':
7426 newEffects.push(ThumbnailHoverEffectExtract('image_translateY_0%_-100%', effects[i]));
7427 break;
7428 case 'IMAGESLIDEDOWN':
7429 newEffects.push(ThumbnailHoverEffectExtract('image_translateY_0%_100%', effects[i]));
7430 break;
7431 case 'LABELSLIDEUPDOWN':
7432 newEffects.push(ThumbnailHoverEffectExtract('label_translateY_0%_100%', effects[i]));
7433 break;
7434 case 'DESCRIPTIONSLIDEUP':
7435 newEffects.push(ThumbnailHoverEffectExtract('description_translateY_110%_0%', effects[i]));
7436 break;
7437
7438 case 'IMAGEBLURON':
7439 newEffects.push(ThumbnailHoverEffectExtract('image_blur_2.00px_0.00px', effects[i]));
7440 break;
7441 case 'IMAGEBLUROFF':
7442 newEffects.push(ThumbnailHoverEffectExtract('image_blur_0.00px_2.00px', effects[i]));
7443 break;
7444 case 'IMAGEGRAYON':
7445 newEffects.push(ThumbnailHoverEffectExtract('image_grayscale_0%_100%', effects[i]));
7446 break;
7447 case 'IMAGEGRAYOFF':
7448 newEffects.push(ThumbnailHoverEffectExtract('image_grayscale_100%_0%', effects[i]));
7449 break;
7450 case 'IMAGESEPIAON':
7451 newEffects.push(ThumbnailHoverEffectExtract('image_sepia_100%_1%', effects[i]));
7452 break;
7453 case 'IMAGESEPIAOFF':
7454 newEffects.push(ThumbnailHoverEffectExtract('image_sepia_1%_100%', effects[i]));
7455 break;
7456
7457 default:
7458 newEffects.push(effects[i]);
7459 break;
7460 }
7461 }
7462
7463 return newEffects;
7464 }
7465
7466
7467 // Thumbnail hover effect definition
7468 function NewTHoverEffect() {
7469 var oDef={
7470 name: '',
7471 element: '', // element class
7472 type: '',
7473 from: '', // start value
7474 to: '', // end value
7475 hoverin: true,
7476 hoverout: true,
7477 firstKeyframe: true,
7478 delay: 0,
7479 delayBack: 0,
7480 duration: 400,
7481 durationBack: 300,
7482 easing: 'easeOutQuart',
7483 easingBack: 'easeOutQuart',
7484 animParam: null
7485 };
7486 return oDef;
7487 }
7488
7489 function NewTBuildInit() {
7490 // to set CSS properties
7491 var oDef={ element: '', property: '', value: '' };
7492 return oDef;
7493 }
7494
7495
7496 function ThumbnailStyle( cfg, level) {
7497
7498 switch( cfg.position ){
7499 case 'onBottom' :
7500 G.tn.style[level]['label'] = 'bottom:0; ';
7501 break;
7502 case 'right' :
7503 switch( cfg.valign ) {
7504 case 'top':
7505 G.tn.style[level]['label'] = 'top:0; position:absolute; left: 50%;';
7506 break;
7507 case 'middle':
7508 G.tn.style[level]['label'] = 'top:0; bottom:0; left: 50%;';
7509 G.tn.style[level]['title'] = 'position:absolute; bottom:50%;';
7510 G.tn.style[level]['desc'] = 'position:absolute; top:50%;';
7511 break;
7512 case 'bottom':
7513 default:
7514 G.tn.style[level].label = 'bottom:0; position:absolute; left: 50%;';
7515 G.tn.style[level].title = 'position:absolute;bottom:0;';
7516 break;
7517 }
7518 break;
7519 case 'custom':
7520 break;
7521 default:
7522 case 'overImage' :
7523 switch( cfg.valign ) {
7524 case 'top':
7525 G.tn.style[level]['label'] = 'top:0; position:absolute;';
7526 break;
7527 case 'middle':
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 'bottom':
7533 default:
7534 // G.O.thumbnailLabel.position = 'overImageOnBottom';
7535 G.tn.style[level].label = 'bottom:0; position:absolute;';
7536 break;
7537 }
7538
7539 // case 'overImageOnTop' :
7540 // G.tn.style[level]['label'] = 'top:0; position:absolute;';
7541 // break;
7542 // case 'overImageOnMiddle' :
7543 // G.tn.style[level]['label'] = 'top:0; bottom:0;';
7544 // G.tn.style[level]['title'] = 'position:absolute; bottom:50%;';
7545 // G.tn.style[level]['desc'] = 'position:absolute; top:50%;';
7546 // break;
7547 // case 'right' :
7548 // case 'custom' :
7549 // break;
7550 // case 'overImageOnBottom' :
7551 // default :
7552 // G.O.thumbnailLabel.position = 'overImageOnBottom';
7553 // G.tn.style[level].label = 'bottom:0; position:absolute;';
7554 // break;
7555 }
7556
7557 // if( G.layout.engine != 'CASCADING' ) {
7558 if( cfg.position != 'onBottom' ) {
7559 // multi-line
7560 if( cfg.titleMultiLine ) {
7561 G.tn.style[level]['title'] += 'white-space:normal;';
7562 }
7563 if( cfg.descriptionMultiLine ) {
7564 G.tn.style[level]['desc'] += 'white-space:normal;';
7565 }
7566 }
7567
7568 // horizontal alignement
7569 switch( cfg.align ) {
7570 case 'right':
7571 G.tn.style[level].label += 'text-align:right;';
7572 break;
7573 case 'left':
7574 G.tn.style[level].label += 'text-align:left;';
7575 break;
7576 default:
7577 G.tn.style[level].label += 'text-align:center;';
7578 break;
7579 }
7580
7581
7582 if( cfg.titleFontSize != undefined && cfg.titleFontSize != '' ) {
7583 G.tn.style[level].title += 'font-size:' + cfg.titleFontSize + ';';
7584 }
7585 if( cfg.descriptionFontSize != undefined && cfg.descriptionFontSize != '' ) {
7586 G.tn.style[level].desc += 'font-size:' + cfg.descriptionFontSize + ';';
7587 }
7588
7589 if( cfg.displayDescription == false ) {
7590 G.tn.style[level].desc += 'display:none;';
7591 }
7592 }
7593
7594
7595 // cache some thumbnail settings
7596 function ThumbnailDefCaches() {
7597 // thumbnail content CSS styles
7598
7599 // settings for level L1 and LN
7600 ThumbnailStyle( G.O.thumbnailLabel, 'lN');
7601 if( G.O.thumbnailL1Label !== undefined ) {
7602 ThumbnailStyle( G.O.thumbnailL1Label, 'l1');
7603 }
7604 else {
7605 ThumbnailStyle( G.O.thumbnailLabel, 'l1');
7606 }
7607
7608 if( G.O.thumbnailL1Label && G.O.thumbnailL1Label.display ) {
7609 // settings for level L1
7610 ThumbnailStyle( G.O.thumbnailL1Label, 'l1');
7611 }
7612
7613
7614 // default thumbnail sizes levels l1 and lN
7615 var lst=['xs','sm','me','la','xl'];
7616 for( var i = 0; i < lst.length; i++ ) {
7617 var w = G.tn.settings.width.lN[lst[i]];
7618 if( w != 'auto' ) {
7619 G.tn.defaultSize.width.lN[lst[i]] = w;
7620 G.tn.defaultSize.width.l1[lst[i]] = w;
7621 }
7622 else {
7623 var h = G.tn.settings.height.lN[lst[i]];
7624 G.tn.defaultSize.width.lN[lst[i]] = h; // dynamic width --> set height value as default for the width
7625 G.tn.defaultSize.width.l1[lst[i]] = h; // dynamic width --> set height value as default
7626 }
7627 }
7628 for( var i = 0; i < lst.length; i++ ) {
7629 var h = G.tn.settings.height.lN[lst[i]];
7630 if( h != 'auto' ) {
7631 // grid or justified layout
7632 G.tn.defaultSize.height.lN[lst[i]] = h; //+G.tn.labelHeight.get();
7633 G.tn.defaultSize.height.l1[lst[i]] = h; //+G.tn.labelHeight.get();
7634 }
7635 else {
7636 var w = G.tn.settings.width.lN[lst[i]];
7637 G.tn.defaultSize.height.lN[lst[i]] = w; // dynamic height --> set width value as default for the height
7638 G.tn.defaultSize.height.l1[lst[i]] = w; // dynamic height --> set width value as default
7639 }
7640 }
7641
7642 // default thumbnail sizes levels l1
7643 for( var i = 0; i < lst.length; i++ ) {
7644 var w = G.tn.settings.width.l1[lst[i]];
7645 if( w != 'auto' ) {
7646 G.tn.defaultSize.width.l1[lst[i]] = w;
7647 }
7648 else {
7649 var h = G.tn.settings.height.l1[lst[i]];
7650 G.tn.defaultSize.width.l1[lst[i]] = h; // dynamic width --> set height value as default
7651 }
7652 }
7653 for( var i = 0; i < lst.length; i++ ) {
7654 var h = G.tn.settings.height.l1[lst[i]];
7655 if( h != 'auto' ) {
7656 // grid or justified layout
7657 G.tn.defaultSize.height.l1[lst[i]] = h; //+G.tn.labelHeight.get();
7658 }
7659 else {
7660 var w = G.tn.settings.width.l1[lst[i]];
7661 G.tn.defaultSize.height.l1[lst[i]]= w ; // dynamic height --> set width value as default
7662 }
7663 }
7664
7665 }
7666
7667
7668 //
7669 function GalleryThemeGetCurrent() {
7670
7671 var cs=null;
7672 switch(toType(G.O.galleryTheme)) {
7673 case 'object': // user custom color scheme object
7674 cs = G.galleryTheme_dark; // default color scheme
7675 jQuery.extend(true,cs,G.O.galleryTheme);
7676 break;
7677 case 'string': // name of an internal defined color scheme
7678 switch( G.O.galleryTheme ) {
7679 case 'light':
7680 cs = G.galleryTheme_light;
7681 break;
7682 case 'default':
7683 case 'dark':
7684 case 'none':
7685 default:
7686 cs = G.galleryTheme_dark;
7687 }
7688 break;
7689 default:
7690 cs = G.galleryTheme_dark;
7691 }
7692 return cs;
7693 }
7694
7695 // ##### BREADCRUMB/THUMBNAIL COLOR SCHEME #####
7696 function SetGalleryTheme() {
7697
7698 if( typeof G.O.colorScheme !== 'undefined' ) {
7699 G.O.galleryTheme = G.O.colorScheme;
7700 }
7701
7702 var cs = null;
7703 var galleryTheme = '';
7704 switch(toType(G.O.galleryTheme)) {
7705 case 'object': // user custom color scheme object
7706 cs = G.galleryTheme_dark; // default color scheme
7707 jQuery.extend(true,cs,G.O.galleryTheme);
7708 galleryTheme='nanogallery_gallerytheme_custom_' + G.baseEltID;
7709 break;
7710 case 'string': // name of an internal defined color scheme
7711 switch( G.O.galleryTheme ) {
7712 case 'light':
7713 cs = G.galleryTheme_light;
7714 galleryTheme='nanogallery_gallerytheme_light_' + G.baseEltID;
7715 break;
7716 case 'default':
7717 case 'dark':
7718 case 'none':
7719 default:
7720 cs = G.galleryTheme_dark;
7721 galleryTheme='nanogallery_gallerytheme_dark_' + G.baseEltID;
7722 }
7723 break;
7724 default:
7725 NanoAlert(G, 'Error in galleryTheme parameter.');
7726 return;
7727 }
7728
7729 //var s1='.nanogallery_theme_'+G.O.theme+' ';
7730 var s1='.' + galleryTheme + ' ';
7731
7732 // navigation bar
7733 var c = cs.navigationBar;
7734 var s=s1+'.nGY2Navigationbar { background:'+c.background+'; }'+'\n';
7735 if( c.border !== undefined && c.border !== '' ) { s+=s1+'.nGY2Navigationbar { border:'+c.border+'; }'+'\n'; }
7736 if( c.borderTop !== undefined && c.borderTop !== '' ) { s+=s1+'.nGY2Navigationbar { border-top:'+c.borderTop+'; }'+'\n'; }
7737 if( c.borderBottom !== undefined && c.borderBottom !== '' ) { s+=s1+'.nGY2Navigationbar { border-bottom:'+c.borderBottom+'; }'+'\n'; }
7738 if( c.borderRight !== undefined && c.borderRight !== '' ) { s+=s1+'.nGY2Navigationbar { border-right:'+c.borderRight+'; }'+'\n'; }
7739 if( c.borderLeft !== undefined && c.borderLeft !== '' ) { s+=s1+'.nGY2Navigationbar { border-left:'+c.borderLeft+'; }'+'\n'; }
7740
7741 // navigation bar - breadcrumb
7742 var c = cs.navigationBreadcrumb;
7743 s+=s1+'.nGY2Breadcrumb { background:'+c.background+'; border-radius:'+c.borderRadius+'; }'+'\n';
7744 s+=s1+'.nGY2Breadcrumb .oneItem { color:'+c.color+'; }'+'\n';
7745 s+=s1+'.nGY2Breadcrumb .oneItem:hover { color:'+c.colorHover+'; }'+'\n';
7746
7747 // navigation bar - tag filter
7748 var c = cs.navigationFilter;
7749 s+=s1+'.nGY2NavFilterUnselected { color:'+c.color+'; background:'+c.background+'; border-radius:'+c.borderRadius+'; }'+'\n';
7750 s+=s1+'.nGY2NavFilterSelected { color:'+c.colorSelected+'; background:'+c.backgroundSelected+'; border-radius:'+c.borderRadius+'; }'+'\n';
7751 s+=s1+'.nGY2NavFilterSelectAll { color:'+c.colorSelected+'; background:'+c.background+'; border-radius:'+c.borderRadius+'; }'+'\n';
7752
7753 // navigation bar - pagination next/previous
7754 var c = cs.navigationPagination;
7755 s+=s1+'.nGY2NavPagination { color:'+c.color+'; background:'+c.background+'; border-radius:'+c.borderRadius+'; }'+'\n';
7756 s+=s1+'.nGY2NavPagination:hover { color:'+c.colorHover+'; }'+'\n';
7757
7758 // thumbnails
7759 var c = cs.thumbnail;
7760 // 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';
7761 s+=s1+'.nGY2GThumbnail { border-radius: '+c.borderRadius+'; background:'+c.background+'; border-color:'+c.borderColor+'; }'+'\n';
7762 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';
7763 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';
7764 s+=s1+'.nGY2GThumbnailStack { background:'+c.stackBackground+'; }'+'\n';
7765 // s+=s1+'.nGY2GThumbnailImage { background:'+cs.thumbnail.background+'; background-image:'+cs.thumbnail.backgroundImage+'; }'+'\n';
7766 s+=s1+'.nGY2TnImgBack { background:'+c.background+'; background-image:'+c.backgroundImage+'; }'+'\n';
7767 s+=s1+'.nGY2GThumbnailAlbumUp { background:'+c.background+'; background-image:'+c.backgroundImage+'; color:'+cs.thumbnail.titleColor+'; }'+'\n';
7768 s+=s1+'.nGY2GThumbnailIconsFullThumbnail { color:'+c.titleColor+'; }\n';
7769 s+=s1+'.nGY2GThumbnailLabel { background:'+c.labelBackground+'; opacity:'+c.labelOpacity+'; }'+'\n';
7770 s+=s1+'.nGY2GThumbnailImageTitle { color:'+c.titleColor+'; background-color:'+c.titleBgColor+'; '+(c.titleShadow =='' ? '': 'Text-Shadow:'+c.titleShadow+';')+' }'+'\n';
7771 s+=s1+'.nGY2GThumbnailAlbumTitle { color:'+c.titleColor+'; background-color:'+c.titleBgColor+'; '+(c.titleShadow =='' ? '': 'Text-Shadow:'+c.titleShadow+';')+' }'+'\n';
7772 s+=s1+'.nGY2GThumbnailDescription { color:'+c.descriptionColor+'; background-color:'+c.descriptionBgColor+'; '+(c.descriptionShadow =='' ? '': 'Text-Shadow:'+c.descriptionShadow+';')+' }'+'\n';
7773
7774 // thumbnails - icons
7775 var c = cs.thumbnailIcon;
7776 s+=s1+'.nGY2GThumbnailIcons { padding:'+c.padding+'; }\n';
7777 s+=s1+'.nGY2GThumbnailIcon { color:'+c.color+'; '+(c.shadow =='' ? '': 'Text-Shadow:'+c.shadow+';')+' }\n';
7778 s+=s1+'.nGY2GThumbnailIconTextBadge { background-color:'+c.color+'; }\n';
7779
7780 // gallery pagination -> dot/rectangle based
7781 var c = cs.pagination;
7782 if( G.O.galleryPaginationMode != 'NUMBERS' ) {
7783 s+=s1+'.nGY2paginationDot { border:'+c.shapeBorder+'; background:'+c.shapeColor+';}\n';
7784 s+=s1+'.nGY2paginationDotCurrentPage { border:'+c.shapeBorder+'; background:'+c.shapeSelectedColor+';}\n';
7785 s+=s1+'.nGY2paginationRectangle { border:'+c.shapeBorder+'; background:'+c.shapeColor+';}\n';
7786 s+=s1+'.nGY2paginationRectangleCurrentPage { border:'+c.shapeBorder+'; background:'+c.shapeSelectedColor+';}\n';
7787 } else {
7788 s+=s1+'.nGY2paginationItem { background:'+c.background+'; color:'+c.color+'; border-radius:'+c.borderRadius+'; }\n';
7789 s+=s1+'.nGY2paginationItemCurrentPage { background:'+c.background+'; color:'+c.color+'; border-radius:'+c.borderRadius+'; }\n';
7790 s+=s1+'.nGY2PaginationPrev { background:'+c.background+'; color:'+c.color+'; border-radius:'+c.borderRadius+'; }\n';
7791 s+=s1+'.nGY2PaginationNext { background:'+c.background+'; color:'+c.color+'; border-radius:'+c.borderRadius+'; }\n';
7792 s+=s1+'.nGY2paginationItemCurrentPage { background:'+c.backgroundSelected+'; }\n';
7793 }
7794
7795 // gallery more button
7796 var c = cs.thumbnail;
7797 // 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';
7798 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';
7799 s+=s1+'.nGY2GalleryMoreButtonAnnotation { color:'+c.titleColor+'; '+(c.titleShadow =='' ? '': 'Text-Shadow:'+c.titleShadow)+'; }\n';
7800
7801 jQuery('head').append('<style id="ngycs_'+G.baseEltID+'">'+s+'</style>');
7802 G.$E.base.addClass(galleryTheme);
7803
7804 };
7805
7806 // ##### VIEWER COLOR SCHEME #####
7807 function SetViewerTheme( ) {
7808
7809 if( G.VOM.viewerTheme != '' ) {
7810 G.VOM.$baseCont.addClass(G.VOM.viewerTheme);
7811 return;
7812 }
7813
7814 if( typeof G.O.colorSchemeViewer !== 'undefined' ) {
7815 G.O.viewerTheme = G.O.colorSchemeViewer;
7816 }
7817
7818 var cs=null;
7819 switch(toType(G.O.viewerTheme)) {
7820 case 'object': // user custom color scheme object
7821 cs = G.viewerTheme_dark;
7822 jQuery.extend(true, cs, G.O.viewerTheme);
7823 G.VOM.viewerTheme = 'nanogallery_viewertheme_custom_' + G.baseEltID;
7824 break;
7825 case 'string': // name of an internal defined color scheme
7826 switch( G.O.viewerTheme ) {
7827 case 'none':
7828 return;
7829 break;
7830 case 'light':
7831 cs = G.viewerTheme_light;
7832 G.VOM.viewerTheme = 'nanogallery_viewertheme_light_' + G.baseEltID;
7833 break;
7834 case 'dark':
7835 case 'default':
7836 cs = G.viewerTheme_dark;
7837 G.VOM.viewerTheme = 'nanogallery_viewertheme_dark_' + G.baseEltID;
7838 break;
7839 }
7840 break;
7841 default:
7842 NanoAlert(G, 'Error in viewerTheme parameter.');
7843 return;
7844 }
7845
7846 var s1 = '.' + G.VOM.viewerTheme + ' ';
7847 var s = s1 + '.nGY2Viewer { background:' + cs.background + '; }'+'\n';
7848 s += s1 + '.nGY2Viewer .toolbarBackground { background:' + cs.barBackground + '; }'+'\n';
7849 s += s1 + '.nGY2Viewer .toolbar { border:' + cs.barBorder + '; color:' + cs.barColor + '; }'+'\n';
7850 s += s1 + '.nGY2Viewer .toolbar .previousButton:after { color:' + cs.barColor + '; }'+'\n';
7851 s += s1 + '.nGY2Viewer .toolbar .nextButton:after { color:' + cs.barColor + '; }'+'\n';
7852 s += s1 + '.nGY2Viewer .toolbar .closeButton:after { color:' + cs.barColor + '; }'+'\n';
7853 s += s1 + '.nGY2Viewer .toolbar .label .title { color:' + cs.barColor + '; }'+'\n';
7854 s += s1 + '.nGY2Viewer .toolbar .label .description { color:' + cs.barDescriptionColor + '; }'+'\n';
7855 jQuery('head').append('<style>' + s + '</style>');
7856 G.VOM.$baseCont.addClass(G.VOM.viewerTheme);
7857 };
7858
7859
7860
7861 /** @function SetPolyFills */
7862 function SetPolyFills() {
7863
7864 // POLYFILL FOR BIND function --> for older Safari mobile
7865 // found on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind#Compatibility
7866 if (!Function.prototype.bind) {
7867 Function.prototype.bind = function (oThis) {
7868 if (typeof this !== "function") {
7869 // closest thing possible to the ECMAScript 5
7870 // internal IsCallable function
7871 throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
7872 }
7873
7874 var aArgs = Array.prototype.slice.call(arguments, 1),
7875 fToBind = this,
7876 fNOP = function () {},
7877 fBound = function () {
7878 return fToBind.apply(this instanceof fNOP && oThis
7879 ? this
7880 : oThis,
7881 aArgs.concat(Array.prototype.slice.call(arguments)));
7882 };
7883
7884 fNOP.prototype = this.prototype;
7885 fBound.prototype = new fNOP();
7886
7887 return fBound;
7888 };
7889 }
7890
7891 // requestAnimationFrame polyfill by Erik M�ller. fixes from Paul Irish and Tino Zijdel
7892 // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
7893 // http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
7894 // MIT license
7895 (function() {
7896 var lastTime = 0;
7897 var vendors = ['ms', 'moz', 'webkit', 'o'];
7898 for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
7899 window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
7900 window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
7901 }
7902 if (!window.requestAnimationFrame)
7903 window.requestAnimationFrame = function(callback, element) {
7904 var currTime = new Date().getTime();
7905 var timeToCall = Math.max(0, 16 - (currTime - lastTime));
7906 var id = window.setTimeout(function() { callback(currTime + timeToCall); }, timeToCall);
7907 lastTime = currTime + timeToCall;
7908 return id;
7909 };
7910
7911 if (!window.cancelAnimationFrame)
7912 window.cancelAnimationFrame = function(id) {
7913 clearTimeout(id);
7914 };
7915 }());
7916
7917 // array.ngy2removeIf -> removes items from array base on a function's result
7918 Array.prototype.ngy2removeIf = function(callback) {
7919 var i = this.length;
7920 while (i--) {
7921 if (callback(this[i], i)) {
7922 this.splice(i, 1);
7923 }
7924 }
7925 };
7926
7927 // IE11 for startsWith
7928 // thanks to @lichtamberg - https://github.com/lichtamberg
7929 if (!String.prototype.startsWith) {
7930 String.prototype.startsWith = function(searchString, position) {
7931 position = position || 0;
7932 return this.indexOf(searchString, position) === position;
7933 };
7934 }
7935
7936 }
7937
7938
7939 // Gallery clicked or toolbar touched -> retrieve & execute action
7940 function GalleryClicked(e) {
7941
7942 var r = GalleryEventRetrieveElementl(e, false);
7943
7944 if( r.GOMidx == -1 ) { return 'exit'; }
7945
7946 var idx = G.GOM.items[r.GOMidx].thumbnailIdx;
7947 if( G.GOM.slider.hostIdx == r.GOMidx ) {
7948 idx = G.GOM.items[G.GOM.slider.currentIdx].thumbnailIdx;
7949 }
7950 switch( r.action ) {
7951 case 'OPEN':
7952 ThumbnailOpen(idx, false);
7953 return 'exit';
7954 break;
7955 case 'DISPLAY':
7956 // used the display icon (ignore if selection mode)
7957 ThumbnailOpen(idx, true);
7958 return 'exit';
7959 break;
7960 case 'TOGGLESELECT':
7961 ThumbnailSelectionToggle(idx);
7962 return 'exit';
7963 break;
7964 case 'SHARE':
7965 PopupShare(idx);
7966 return 'exit';
7967 break;
7968 case 'DOWNLOAD':
7969 DownloadImage(idx);
7970 return 'exit';
7971 break;
7972 case 'INFO':
7973 ItemDisplayInfo(G.I[idx]);
7974 return 'exit';
7975 break;
7976 case 'SHOPPINGCART':
7977 AddToCart(idx, 'gallery');
7978 return 'exit';
7979 break;
7980 default:
7981 // all other actions (custom1..10, or anything else)
7982 var fu = G.O.fnThumbnailToolCustAction;
7983 if( fu !== null ) {
7984 typeof fu == 'function' ? fu(r.action, G.I[idx]) : window[fu](r.action, G.I[idx]);
7985 }
7986 break;
7987 }
7988 }
7989
7990 // Download an image
7991 function DownloadImage(idx) {
7992 if( G.I[idx].mediaKind != 'img' ) { return; }
7993
7994
7995 var url = G.I[idx].src;
7996
7997 if( G.I[idx].downloadURL != undefined && G.I[idx].downloadURL != '' ) {
7998 url = G.I[idx].downloadURL;
7999 }
8000
8001 var a = document.createElement('a');
8002 a.href = url;
8003 // a.download = url.split('.').pop();
8004 a.download = url.split('/').pop();
8005 a.target = '_blank';
8006 a.style.display = 'none';
8007 document.body.appendChild(a);
8008 a.click();
8009 document.body.removeChild(a);
8010
8011 }
8012
8013 // add one image to the shopping cart
8014 function AddToCart( idx, source ) {
8015 // increment quantity if already in shopping cart
8016 var found=false;
8017 for( var i=0; i<G.shoppingCart.length; i++ ) {
8018 if( G.shoppingCart[i].idx == idx ) {
8019 G.shoppingCart[i].qty++;
8020 ThumbnailBuildToolbarOneCartUpdate( G.I[idx] );
8021
8022 var fu = G.O.fnShoppingCartUpdated;
8023 if( fu !== null ) {
8024 typeof fu == 'function' ? fu(G.shoppingCart, G.I[idx], source) : window[fu](G.shoppingCart, G.I[idx], source);
8025 }
8026 TriggerCustomEvent('shoppingCartUpdated');
8027 return;
8028 }
8029 }
8030
8031 // add to shopping cart
8032 if( !found) {
8033 G.shoppingCart.push( { idx:idx, ID:G.I[idx].GetID(), qty:1} );
8034 ThumbnailBuildToolbarOneCartUpdate(G.I[idx]);
8035
8036 var fu=G.O.fnShoppingCartUpdated;
8037 if( fu !== null ) {
8038 typeof fu == 'function' ? fu(G.shoppingCart, G.I[idx], source) : window[fu](G.shoppingCart, G.I[idx], source);
8039 }
8040 TriggerCustomEvent('shoppingCartUpdated');
8041 }
8042 }
8043
8044
8045 // All thumbnails are set to unselected
8046 function ThumbnailSelectionClear() {
8047 G.GOM.nbSelected = 0;
8048 for( var i = 0, nbTn = G.GOM.items.length; i < nbTn ; i++ ) {
8049 var item = G .I[G.GOM.items[i].thumbnailIdx];
8050 if( item.selected ) {
8051 item.selected = false;
8052 var fu = G.O.fnThumbnailSelection;
8053 if( fu !== null ) {
8054 typeof fu == 'function' ? fu(item.$elt, item, G.I) : window[fu](item.$elt, item, G.I);
8055 }
8056 }
8057 item.selected = false;
8058 }
8059 }
8060
8061 function ThumbnailSelectionToggle( idx ){
8062 var item = G.I[idx];
8063 if( item.selected === true ) {
8064 ThumbnailSelectionSet(item, false);
8065 G.GOM.nbSelected--;
8066 TriggerCustomEvent('itemUnSelected');
8067 }
8068 else {
8069 ThumbnailSelectionSet(item, true);
8070 G.GOM.nbSelected++;
8071 TriggerCustomEvent('itemSelected');
8072 }
8073 }
8074
8075
8076 // this replaces ThumbnailSelection()
8077 function ThumbnailSelectionSet(item, selected ){
8078
8079 item.selected = selected;
8080
8081 ThumbnailSelectionSetIcon( item );
8082
8083 // called when the selection status of an item changed
8084 var fu=G.O.fnThumbnailSelection;
8085 if( fu !== null ) {
8086 typeof fu == 'function' ? fu(item.$elt, item, G.I) : window[fu](item.$elt, item, G.I);
8087 }
8088
8089 }
8090
8091 function ThumbnailSelectionSetIcon( item ) {
8092 if( item.$elt == null ) {
8093 // thumbnail is not built
8094 return;
8095 }
8096 var $sub = item.$getElt('.nGY2GThumbnail');
8097 var $icon = item.$getElt('.nGY2GThumbnailIconImageSelect');
8098 if( item.selected === true) {
8099 $sub.addClass('nGY2GThumbnailSubSelected');
8100 $icon.addClass('nGY2ThumbnailSelected');
8101 $icon.removeClass('nGY2ThumbnailUnselected');
8102 $icon.html(G.O.icons.thumbnailSelected);
8103 }
8104 else {
8105 $sub.removeClass('nGY2GThumbnailSubSelected');
8106 $icon.removeClass('nGY2ThumbnailSelected');
8107 $icon.addClass('nGY2ThumbnailUnselected');
8108 $icon.html(G.O.icons.thumbnailUnselected);
8109 }
8110 }
8111
8112
8113 // display a modal popup for sharing image/album
8114 function PopupShare(idx) {
8115
8116 // SEE SAMPLES: https://gist.github.com/chrisjlee/5196139
8117 // https://github.com/Julienh/Sharrre
8118
8119 var item=G.I[idx];
8120
8121 var currentURL=document.location.protocol + '//' + document.location.hostname + document.location.pathname;
8122 var newLocationHash = '#nanogallery/' + G.baseEltID + '/';
8123 if( item.kind == 'image' ) {
8124 newLocationHash += item.albumID + '/' + item.GetID();
8125 }
8126 else {
8127 newLocationHash += item.GetID();
8128 }
8129
8130 var content = '<br><br>';
8131 content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="facebook">' + G.O.icons.shareFacebook + '</div>';
8132 content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="pinterest">' + G.O.icons.sharePinterest + '</div>';
8133 content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="tumblr">' + G.O.icons.shareTumblr + '</div>';
8134 content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="twitter">' + G.O.icons.shareTwitter + '</div>';
8135 // content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="googleplus">' + G.O.icons.shareGooglePlus + '</div>';
8136 content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="vk">' + G.O.icons.shareVK + '</div>';
8137 content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="mail">' + G.O.icons.shareMail + '</div>';
8138 content += '<div class="nGY2PopupOneItem" style="text-align:center;"></div>';
8139 content += '<input class="nGY2PopupOneItemText" readonly type="text" value="' + currentURL+newLocationHash + '" style="width:100%;text-align:center;">';
8140 content += '<br>';
8141
8142 currentURL = encodeURIComponent(document.location.protocol + '//' + document.location.hostname + document.location.pathname + newLocationHash);
8143
8144 var currentTitle = item.title;
8145 var currentTn = item.thumbImg().src;
8146
8147
8148 Popup('nanogallery2 - share to:', content, 'Center');
8149
8150 G.popup.$elt.find('.nGY2PopupOneItem').on('click', function(e) {
8151 e.stopPropagation();
8152
8153 var shareURL = '';
8154 var found = true;
8155 switch(jQuery(this).attr('data-share').toUpperCase()) {
8156 case 'FACEBOOK':
8157 // <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>
8158 //window.open("https://www.facebook.com/sharer.php?u="+currentURL,"","height=368,width=600,left=100,top=100,menubar=0");
8159 shareURL = 'https://www.facebook.com/sharer.php?u=' + currentURL;
8160 break;
8161 case 'VK':
8162 shareURL = 'http://vk.com/share.php?url=' + currentURL;
8163 break;
8164 case 'GOOGLEPLUS':
8165 shareURL = "https://plus.google.com/share?url=" + currentURL;
8166 break;
8167 case 'TWITTER':
8168 // shareURL="https://twitter.com/share?url="+currentURL+"&text="+currentTitle;
8169 shareURL = 'https://twitter.com/intent/tweet?text=' + currentTitle + 'url=' + currentURL;
8170 break;
8171 case 'PINTEREST':
8172 // shareURL='https://pinterest.com/pin/create/bookmarklet/?media='+currentTn+'&url='+currentURL+'&description='+currentTitle;
8173 shareURL = 'https://pinterest.com/pin/create/button/?media=' + currentTn + '&url=' + currentURL + '&description=' + currentTitle;
8174 break;
8175 case 'TUMBLR':
8176 //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;
8177 shareURL = 'http://www.tumblr.com/share/link?url=' + currentURL + '&name=' + currentTitle;
8178 break;
8179 case 'MAIL':
8180 shareURL = 'mailto:?subject=' + currentTitle + '&body=' + currentURL;
8181 break;
8182 default:
8183 found = false;
8184 break;
8185 }
8186
8187 if( found ) {
8188 window.open(shareURL, "" , "height=550,width=500,left=100,top=100,menubar=0" );
8189 G.popup.close();
8190 // $popup.remove();
8191 }
8192
8193 });
8194 }
8195
8196 // build a modal popup
8197 function Popup(title, content, align) {
8198 var pp = '<div class="nGY2Popup" style="opacity:0;"><div class="nGY2PopupContent' + align + '">';
8199 pp += '<div class="nGY2PopupCloseButton" style="font-size:0.9em;">' + G.O.icons.buttonClose + '</div>';
8200 pp += '<div class="nGY2PopupTitle">' + title + '</div>';
8201 pp += content;
8202 pp += '</div></div>';
8203
8204 G.popup.$elt = jQuery(pp).appendTo('body');
8205 setElementOnTop( G.VOM.$viewer, G.popup.$elt);
8206
8207 G.popup.isDisplayed = true;
8208
8209 var tweenable = new NGTweenable();
8210 tweenable.tween({
8211 from: { o: 0, y: 100 },
8212 to: { o: 1, y: 0 },
8213 easing: 'easeInOutSine',
8214 duration: 250,
8215 step: function (state, att) {
8216 G.popup.$elt[0].style.opacity = state.o;
8217 G.popup.$elt[0].style[G.CSStransformName] = 'translateY(' + (state.y) + 'px)';
8218 }
8219 });
8220
8221 G.popup.$elt.find('.nGY2PopupCloseButton').on('click', function(e) {
8222 e.stopPropagation();
8223 G.popup.close();
8224 });
8225
8226 }
8227
8228
8229 function GalleryMouseEnter(e) {
8230 if( !G.VOM.viewerDisplayed && G.GOM.albumIdx != -1 ) {
8231 var r = GalleryEventRetrieveElementl(e, true);
8232 // if( r.action == 'OPEN' && r.GOMidx != -1 ) {
8233 if( r.GOMidx != -1 ) {
8234 // var target = e.target || e.srcElement;
8235 // if( target.getAttribute('class') != 'nGY2GThumbnail' ) { return; }
8236 ThumbnailHover(r.GOMidx);
8237 }
8238 }
8239 }
8240
8241 function GalleryMouseLeave(e) {
8242 if( !G.VOM.viewerDisplayed && G.GOM.albumIdx != -1 ) {
8243 var r = GalleryEventRetrieveElementl(e, true);
8244 if( r.GOMidx != -1 ) {
8245 // var target = e.target || e.srcElement;
8246 // if( target.getAttribute('class') != 'nGY2GThumbnail' ) { return; }
8247 ThumbnailHoverOut(r.GOMidx);
8248 }
8249 }
8250 }
8251
8252 function GalleryEventRetrieveElementl( e, ignoreSubItems ) {
8253 var r = { action: 'NONE', GOMidx: -1 };
8254
8255 if( e == undefined ) {
8256 return r;
8257 }
8258 var target = e.target || e.srcElement;
8259 while( target != G.$E.conTnParent[0] ) { // loop element parent up to find the thumbnail element
8260 if( jQuery(target).hasClass('nGY2GThumbnail') ) {
8261 if( r.action == 'NONE' ) {
8262 r.action = 'OPEN';
8263 }
8264 r.GOMidx = jQuery(target).data('index');
8265 return r;
8266 }
8267 // if( !ignoreSubItems && jQuery(target).hasClass('nGY2GThumbnailIcon') ) {
8268 if( !ignoreSubItems ) {
8269 var a = jQuery(target).data('ngy2action');
8270 if( a != '' && a != undefined ) {
8271 r.action = a;
8272 }
8273 }
8274 if( target.parentNode == null ) {
8275 return r;
8276 }
8277 target = target.parentNode;
8278 }
8279 return r;
8280 }
8281
8282
8283 // OPEN ONE THUMBNAIL
8284 function ThumbnailOpen( idx, ignoreSelected ) {
8285 var item = G.I[idx];
8286
8287 G.GOM.albumIdxLoading = idx; // store idx -> may be used to display loader on album thumbnail
8288
8289 var fu = G.O.fnThumbnailClicked;
8290 if( fu !== null ) {
8291 typeof fu == 'function' ? fu(item.$elt, item) : window[fu](item.$elt, item);
8292 }
8293
8294 // open URL
8295 if( item.destinationURL !== undefined && item.destinationURL.length > 0 ) {
8296 window.location = item.destinationURL;
8297 return;
8298 }
8299
8300 switch( item.kind ) {
8301 case 'image':
8302 if( ignoreSelected === false && G.GOM.nbSelected > 0 ) {
8303 ThumbnailSelectionToggle(idx);
8304 }
8305 else {
8306 // display image
8307 DisplayPhotoIdx( idx );
8308 }
8309 break;
8310 case 'album':
8311 if( ignoreSelected === false && G.GOM.nbSelected > 0 ) {
8312 ThumbnailSelectionToggle( idx );
8313 }
8314 else {
8315 if( G.O.thumbnailAlbumDisplayImage && idx != 0 ) {
8316 // display album content in lightbox
8317 DisplayFirstMediaInAlbum( idx );
8318 return;
8319 }
8320 else {
8321 // display album content in gallery
8322 DisplayAlbum('-1', item.GetID());
8323 }
8324 }
8325 break;
8326 case 'albumUp':
8327 var parent = NGY2Item.Get(G, item.albumID);
8328 DisplayAlbum('-1', parent.albumID);
8329 break;
8330 }
8331 }
8332
8333 function DisplayFirstMediaInAlbum( albumIdx ) {
8334 if( G.O.debugMode ) { console.log('#DisplayFirstPhotoInAlbum : '+ albumIdx); }
8335
8336 var item = G.I[albumIdx];
8337
8338 var l = G.I.length;
8339 for( var i = 0; i < l; i++ ) {
8340 if( G.I[i].albumID == item.GetID() ) {
8341 DisplayPhotoIdx( i );
8342 return;
8343 }
8344 }
8345
8346 // load album content
8347 AlbumGetContent( item.GetID(), DisplayFirstMediaInAlbum, albumIdx, null );
8348
8349 }
8350
8351
8352 // Open link to original image (new window)
8353 function OpenOriginal( item ) {
8354 switch( G.O.kind ) {
8355 case 'flickr':
8356 var sU = 'https://www.flickr.com/photos/' + G.O.userID + '/' + item.GetID();
8357 if( item.albumID != '0' ) {
8358 sU += '/in/album-' + item.albumID + '/';
8359 }
8360 window.open(sU, '_blank');
8361 break;
8362 case 'picasa':
8363 case 'google':
8364 case 'google2':
8365 // no more working since Google changed the access to Google Photos in 2017
8366 // var sU='https://plus.google.com/photos/'+G.O.userID+'/albums/'+item.albumID+'/'+item.GetID();
8367 // window.open(sU,'_blank');
8368 // break;
8369 default:
8370 var sU = item.responsiveURL();
8371 window.open(sU, '_blank');
8372 break;
8373 }
8374 }
8375
8376 // ########################################################
8377 // DISPLAY ONE MEDIA
8378 // with internal or external viewer
8379 // ########################################################
8380 function DisplayPhotoIdx( ngy2ItemIdx ) {
8381
8382 if( !G.O.thumbnailOpenInLightox ) { return; }
8383
8384 if( G.O.thumbnailOpenOriginal ) {
8385 // Open link to original image
8386 OpenOriginal( G.I[ngy2ItemIdx] );
8387 return;
8388 }
8389
8390 var items = [];
8391// G.VOM.currItemIdx = 0;
8392 G.VOM.content.current.vIdx = 0;
8393 G.VOM.items = [];
8394 G.VOM.albumID = G.I[ngy2ItemIdx].albumID;
8395
8396 var vimg = new VImg(ngy2ItemIdx);
8397 G.VOM.items.push(vimg);
8398 items.push(G.I[ngy2ItemIdx]);
8399 //TODO -> danger? -> pourquoi reconstruire la liste si d�j� ouvert (back/forward)
8400 var l = G.I.length;
8401 for( let idx = ngy2ItemIdx+1; idx < l ; idx++) {
8402 let item = G.I[idx];
8403 if( item.kind == 'image' && item.isToDisplay(G.VOM.albumID) && item.destinationURL == '' ) {
8404 let vimg = new VImg(idx);
8405 G.VOM.items.push(vimg);
8406 items.push(item);
8407 }
8408 }
8409 var last = G.VOM.items.length;
8410 var cnt = 1;
8411 for( let idx = 0; idx < ngy2ItemIdx ; idx++) {
8412 let item = G.I[idx];
8413 if( item.kind == 'image' && item.isToDisplay(G.VOM.albumID) && item.destinationURL == '' ) {
8414 let vimg = new VImg(idx);
8415 vimg.mediaNumber = cnt;
8416 G.VOM.items.push(vimg);
8417 items.push(item);
8418 cnt++;
8419 }
8420 }
8421 for( let i = 0; i < last; i++ ) {
8422 G.VOM.items[i].mediaNumber = cnt;
8423 cnt++;
8424 }
8425
8426 // opens media with external viewer
8427 var fu = G.O.fnThumbnailOpen;
8428 if( fu !== null ) {
8429 typeof fu == 'function' ? fu(items) : window[fu](items);
8430 return;
8431 }
8432
8433 // use internal viewer
8434 if( !G.VOM.viewerDisplayed ) {
8435 // build viewer and display
8436 LightboxOpen();
8437 }
8438 else {
8439 // viewer already displayed -> display new media in current viewer
8440 G.VOM.content.current.$media.empty();
8441 let item = G.VOM.content.current.NGY2Item();
8442 var spreloader = '<div class="nGY2ViewerMediaLoaderDisplayed"></div>';
8443 if( item.mediaKind == 'img' && item.imageWidth != 0 && item.imageHeight != 0 ) {
8444 spreloader = '<div class="nGY2ViewerMediaLoaderHidden"></div>';
8445 }
8446 G.VOM.content.current.$media.append( spreloader + item.mediaMarkup);
8447 ViewerSetMediaVisibility(G.VOM.content.next, 0);
8448 ViewerSetMediaVisibility(G.VOM.content.previous, 0);
8449 if( item.mediaKind == 'img' ) {
8450 G.VOM.ImageLoader.loadImage(VieweImgSizeRetrieved, item);
8451 }
8452 // G.VOM.$mediaCurrent.css({ opacity:0 }).attr('src','');
8453 // G.VOM.ImageLoader.loadImage(VieweImgSizeRetrieved, G.VOM.NGY2Item(0));
8454 // G.VOM.$mediaCurrent.children().eq(0).attr('src',G.emptyGif).attr('src', G.VOM.NGY2Item(0).responsiveURL());
8455 // LightboxDisplay(0, '');
8456 LightboxDisplay('');
8457 }
8458 }
8459
8460 function ViewerZoomStart() {
8461 if( G.O.viewerZoom && !G.VOM.viewerMediaIsChanged ) {
8462 var item = G.VOM.content.current.NGY2Item();
8463 if( item.mediaKind == 'img' && item.imageHeight > 0 && item.imageWidth > 0 ) {
8464 if( G.VOM.zoom.isZooming === false ) {
8465 // default zoom
8466 G.VOM.zoom.userFactor = 1;
8467 G.VOM.zoom.isZooming = true;
8468 }
8469 return true;
8470 }
8471 }
8472 return false;
8473 }
8474
8475 function ViewerZoomIn( zoomIn ) {
8476 if( zoomIn ) {
8477 // zoom in
8478 G.VOM.zoom.userFactor += 0.1;
8479 ViewerZoomMax();
8480 }
8481 else {
8482 // zoom out
8483 G.VOM.zoom.userFactor -= 0.1;
8484 ViewerZoomMin();
8485 }
8486 ViewerMediaSetPosAndZoom();
8487 }
8488
8489 function ViewerZoomMax() {
8490 if( G.VOM.zoom.userFactor > 3 ) {
8491 G.VOM.zoom.userFactor = 3;
8492 }
8493 }
8494 function ViewerZoomMin() {
8495
8496 if( G.VOM.zoom.userFactor < 0.2 ) {
8497 G.VOM.zoom.userFactor = 0.2;
8498 }
8499 }
8500
8501
8502
8503 // Set position and size of all 3 media containers
8504 function ViewerMediaSetPosAndZoom() {
8505
8506 if( !G.VOM.zoom.isZooming ) {
8507 G.VOM.zoom.userFactor = 1;
8508 }
8509 // window.ng_draf( function() {
8510 ViewerMediaSetPosAndZoomOne( G.VOM.content.current, true );
8511 ViewerMediaSetPosAndZoomOne( G.VOM.content.previous, false );
8512 ViewerMediaSetPosAndZoomOne( G.VOM.content.next, false );
8513 // });
8514 }
8515
8516
8517
8518 // Media which is not IMG -> center and set size
8519 function ViewerMediaCenterNotImg( $mediaContainer ) {
8520 var $media = $mediaContainer.children().eq(1);
8521 var h = 90;
8522 if( G.O.viewerGallery != 'none' ) { h -= 10; }
8523 if( G.O.viewerToolbar.display != 'none' ) { h -= 10; }
8524 $media.css( {'height': h+'%' });
8525 $media.css( {'width': '90%' });
8526 $media[0].style[G.CSStransformName] = 'translate(0px, "50%") ';
8527 }
8528
8529 // Set position and size of ONE media container
8530 function ViewerMediaSetPosAndZoomOne(content_item, isCurrent ) {
8531
8532 var item = content_item.NGY2Item();
8533 var $img = content_item.$media;
8534
8535
8536 if( item.mediaKind != 'img' ) {
8537 ViewerMediaCenterNotImg( $img );
8538 return;
8539 }
8540
8541 if( item.imageHeight == 0 || item.imageWidth == 0 ) {
8542 // ViewerSetMediaVisibility( item, $img, 0 );
8543 ViewerSetMediaVisibility( content_item, 0 );
8544 return;
8545 }
8546
8547 // part 1: set the image size
8548 var zoomUserFactor = isCurrent == true ? G.VOM.zoom.userFactor : 1;
8549
8550 var dpr = 1;
8551 if( G.O.viewerImageDisplay == 'bestImageQuality' ) {
8552 dpr = window.devicePixelRatio;
8553 }
8554
8555 // retrieve the base zoom factor (image fill screen)
8556 var zoomBaseFactorW = (G.VOM.window.lastWidth - G.VOM.padding.V) / (item.imageWidth / dpr);
8557 var zoomBaseFactorH = (G.VOM.window.lastHeight - G.VOM.padding.H) / (item.imageHeight / dpr);
8558 var zoomBaseFactor = Math.min(zoomBaseFactorW, zoomBaseFactorH);
8559 if( zoomBaseFactor > 1 && G.O.viewerImageDisplay != 'upscale' ) {
8560 // no upscale
8561 zoomBaseFactor = 1;
8562 }
8563
8564 var imageCurrentHeight = (item.imageHeight / dpr) * zoomUserFactor * zoomBaseFactor;
8565 var imageCurrentWidth = (item.imageWidth / dpr) * zoomUserFactor * zoomBaseFactor;
8566 $img.children().eq(1).css( {'height': imageCurrentHeight });
8567 $img.children().eq(1).css( {'width': imageCurrentWidth });
8568
8569 // retrieve posX/Y to center image
8570 var posX = 0;
8571 if( imageCurrentWidth > G.VOM.window.lastWidth ) {
8572 posX = -(imageCurrentWidth - G.VOM.window.lastWidth)/2;
8573 }
8574 var posY = 0;
8575 // if( imageCurrentHeight > G.VOM.window.lastHeight ) {
8576 // posY = ( imageCurrentHeight - G.VOM.window.lastHeight ) / 2;
8577 // }
8578 // posY = 0; // actually, it seems that the image is always centered vertically -> so no need to to anything
8579
8580 // Part 2: set the X/Y position (for zoom/pan)
8581 if( isCurrent ) {
8582 if( !G.VOM.zoom.isZooming ) {
8583 G.VOM.panPosX = 0;
8584 G.VOM.panPosY = 0;
8585 }
8586 G.VOM.zoom.posX = posX;
8587 G.VOM.zoom.posY = posY;
8588 ViewerImagePanSetPosition(G.VOM.panPosX, G.VOM.panPosY, $img, false);
8589 }
8590 // else {
8591 //$img[0].style[G.CSStransformName]= 'translate3D('+ posX+'px, '+ posY+'px, 0) ';
8592 // }
8593 else {
8594 // set the pan position of each media container
8595 ViewerMediaPanX( G.VOM.swipePosX );
8596 $img.children().eq(1)[0].style[G.CSStransformName]= 'translate(0px, 0px) rotate('+ item.rotationAngle +'deg)';
8597 }
8598
8599 }
8600
8601 // position the image depending on the zoom factor and the pan X/Y position
8602 // IMG is the only media kind supporting zoom/pan
8603 function ViewerImagePanSetPosition(posX, posY, imageContainer, savePosition ) {
8604 if( savePosition ) {
8605 G.VOM.panPosX = posX;
8606 G.VOM.panPosY = posY;
8607 }
8608
8609 posX += G.VOM.zoom.posX;
8610 posY += G.VOM.zoom.posY;
8611
8612 // imageContainer.children().eq(1)[0].style[G.CSStransformName]= 'translate('+ posX + 'px, '+ posY + 'px)';
8613 imageContainer.children().eq(1)[0].style[G.CSStransformName]= 'translate('+ posX + 'px, '+ posY + 'px) rotate('+ G.VOM.content.current.NGY2Item().rotationAngle +'deg)';
8614
8615
8616 }
8617
8618
8619 // LIGHTBOX
8620 // display media with internal viewer
8621 function LightboxOpen( idx ) {
8622
8623 // G.VOM.viewerDisplayed = true;
8624 G.GOM.firstDisplay = false;
8625
8626 // remove scrollbar and add right margin with same width as the scrollbar to avoid page reflow
8627 jQuery('head').append('<style id="nGY2_body_scrollbar_style" type="text/css">.nGY2_body_scrollbar{margin-right: ' + (window.innerWidth - document.documentElement.clientWidth) + 'px;}</style>');
8628 jQuery("body").addClass("nGY2_body_scrollbar");
8629
8630
8631 G.VOM.$baseCont = jQuery('<div class="nGY2 nGY2ViewerContainer" style="opacity:1"></div>').appendTo('body');
8632
8633 SetViewerTheme();
8634
8635 G.VOM.$viewer = jQuery('<div class="nGY2Viewer" style="opacity:0" itemscope itemtype="http://schema.org/ImageObject"></div>').appendTo( G.VOM.$baseCont );
8636 G.VOM.$viewer.css({ msTouchAction: 'none', touchAction: 'none' }); // avoid pinch zoom
8637
8638 if( idx == undefined ) {
8639 G.VOM.content.current.vIdx = 0;
8640 }
8641 else {
8642 G.VOM.content.current.vIdx = idx;
8643 }
8644 G.VOM.content.previous.vIdx = G.VOM.IdxNext();
8645 G.VOM.content.next.vIdx = G.VOM.IdxPrevious();
8646
8647 var sMedia = '<div class="nGY2ViewerMediaPan"><div class="nGY2ViewerMediaLoaderDisplayed"></div>' + G.VOM.content.previous.NGY2Item().mediaMarkup + '</div>'; // previous media
8648 sMedia += '<div class="nGY2ViewerMediaPan"><div class="nGY2ViewerMediaLoaderDisplayed"></div>' + G.VOM.content.current.NGY2Item().mediaMarkup + '</div>'; // current media
8649 sMedia += '<div class="nGY2ViewerMediaPan"><div class="nGY2ViewerMediaLoaderDisplayed"></div>' + G.VOM.content.next.NGY2Item().mediaMarkup + '</div>'; // next media
8650
8651 var sNav = '';
8652 var iconP = G.O.icons.viewerImgPrevious;
8653 if( iconP != undefined && iconP != '') {
8654 sNav += '<div class="nGY2ViewerAreaPrevious ngy2viewerToolAction" data-ngy2action="previous">' + iconP + '</div>';
8655 }
8656 var iconN = G.O.icons.viewerImgNext;
8657 if( iconN != undefined && iconN != '') {
8658 sNav += '<div class="nGY2ViewerAreaNext ngy2viewerToolAction" data-ngy2action="next">' + iconN + '</div>';
8659 }
8660
8661 G.VOM.$content = jQuery('<div class="nGY2ViewerContent">' + sMedia + sNav + '</div>').appendTo( G.VOM.$viewer );
8662
8663 G.VOM.$buttonLeft = G.VOM.$content.find('.nGY2ViewerAreaPrevious');
8664 G.VOM.$buttonRight = G.VOM.$content.find('.nGY2ViewerAreaNext');
8665
8666 var $mediaPan = G.VOM.$content.find('.nGY2ViewerMediaPan');
8667 G.VOM.content.previous.$media = $mediaPan.eq(0); // pointer to previous media container
8668 G.VOM.content.current.$media = $mediaPan.eq(1); // pointer to current media container
8669 G.VOM.content.next.$media = $mediaPan.eq(2); // pointer to next media container
8670
8671 // position next/previous media
8672 var vP = G.GOM.cache.viewport;
8673 G.VOM.content.previous.$media[0].style[G.CSStransformName] = 'translate(-' + vP.w + 'px, 0px)';
8674 G.VOM.content.next.$media[0].style[G.CSStransformName] = 'translate(' + vP.w + 'px, 0px)';
8675
8676
8677 G.VOM.ImageLoader.loadImage( VieweImgSizeRetrieved, G.VOM.content.current.NGY2Item() );
8678 G.VOM.ImageLoader.loadImage( VieweImgSizeRetrieved, G.VOM.content.previous.NGY2Item() );
8679 G.VOM.ImageLoader.loadImage( VieweImgSizeRetrieved, G.VOM.content.next.NGY2Item() );
8680
8681 G.VOM.padding.H = parseInt(G.VOM.$content.css("padding-left")) + parseInt(G.VOM.$content.css("padding-right"));
8682 G.VOM.padding.V = parseInt(G.VOM.$content.css("padding-top")) + parseInt(G.VOM.$content.css("padding-bottom"));
8683
8684 // build media toolbar container
8685 var vtbBg1 = '';
8686 var vtbBg2 = ' toolbarBackground';
8687 if( G.O.viewerToolbar.fullWidth ) {
8688 vtbBg1 = ' toolbarBackground';
8689 vtbBg2 = '';
8690 }
8691 var vtbAlign = 'text-align:center;';
8692 switch ( G.O.viewerToolbar.align ) {
8693 case 'left':
8694 vtbAlign = 'text-align:left;';
8695 break;
8696 case 'right':
8697 vtbAlign = 'text-align:right;';
8698 break;
8699 }
8700 var sTB = '<div class="toolbarContainer nGEvent' + vtbBg1 + '" style="visibility:' +(G.O.viewerToolbar.display ? "visible" : "hidden")+';'+vtbAlign+'"><div class="toolbar nGEvent' + vtbBg2 + '"></div></div>';
8701 G.VOM.$toolbar = jQuery(sTB).appendTo(G.VOM.$viewer);
8702
8703 if( G.VOM.toolbarMode == 'min' || (G.O.viewerToolbar.autoMinimize > 0 && G.O.viewerToolbar.autoMinimize >= G.GOM.cache.viewport.w) ) {
8704 ViewerToolbarForVisibilityMin();
8705 }
8706 else {
8707 ViewerToolbarForVisibilityStd();
8708 }
8709
8710 // top-left toolbar
8711 var sTopLeft = '<div class="nGY2ViewerToolsTopLeft nGEvent"><div class="toolbar nGEvent">';
8712 var sTL = G.O.viewerTools.topLeft.split(',');
8713 for( var i = 0, sTLL = sTL.length; i < sTLL; i++) {
8714 sTopLeft += ToolbarAddElt( sTL[i] );
8715 }
8716 sTopLeft += '</div></div>';
8717 G.VOM.$toolbarTL = jQuery(sTopLeft).appendTo(G.VOM.$viewer);
8718
8719 // top-right toolbar
8720 var sTopRight = '<div class="nGY2ViewerToolsTopRight nGEvent"><div class="toolbar nGEvent">';
8721 var sTR = G.O.viewerTools.topRight.split(',');
8722 for( var i = 0, sTRL = sTR.length; i < sTRL; i++) {
8723 sTopRight += ToolbarAddElt( sTR[i] );
8724 }
8725 sTopRight += '</div></div>';
8726 G.VOM.$toolbarTR = jQuery(sTopRight).appendTo(G.VOM.$viewer);
8727
8728 // set the events handler on the toolbars
8729 ViewerToolsOn();
8730
8731 // Go to fullscreen mode
8732 if( ngscreenfull.enabled && G.O.viewerFullscreen ) {
8733 ngscreenfull.request();
8734 G.VOM.viewerIsFullscreen=true;
8735 }
8736
8737 // Gallery
8738 LightboxGalleryBuild();
8739
8740 setElementOnTop('', G.VOM.$viewer);
8741 ResizeLightbox(true);
8742 G.VOM.gallery.Resize();
8743 G.VOM.timeImgChanged = new Date().getTime();
8744
8745 // viewer display transition
8746 G.VOM.$toolbarTL.css('opacity', 0);
8747 G.VOM.$toolbarTR.css('opacity', 0);
8748 G.VOM.$buttonLeft.css('opacity', 0);
8749 G.VOM.$buttonRight.css('opacity', 0);
8750 if( G.O.viewerGallery != 'none' ) { G.VOM.gallery.$elt.css('opacity', 0); }
8751 G.VOM.$content.css('opacity', 0);
8752 G.VOM.$toolbarTR[0].style[G.CSStransformName] = 'translateY(-40px) ';
8753 G.VOM.$toolbarTL[0].style[G.CSStransformName] = 'translateY(-40px) ';
8754 G.VOM.$buttonLeft[0].style[G.CSStransformName] = 'translateX(-40px) ';
8755 G.VOM.$buttonRight[0].style[G.CSStransformName] = 'translateX(40px) ';
8756
8757 // STEP 1: display main container, including media
8758 new NGTweenable().tween({
8759 from: { opacity: 0, posY: G.VOM.window.lastHeight*.5 },
8760 to: { opacity: 1, posY: 0 },
8761 delay: 10,
8762 duration: 450,
8763 easing: 'easeInOutQuint',
8764 step: function (state) {
8765 // lightbox
8766 G.VOM.$viewer.css('opacity', state.opacity);
8767 G.VOM.$viewer[0].style[G.CSStransformName] = 'translateY(' + (state.posY) + 'px) ';
8768
8769 // media in lightbox
8770 G.VOM.$content.css('opacity', state.opacity);
8771 }
8772 });
8773
8774
8775 // STEP 2: display tools, left/right navigation buttons, gallery
8776 new NGTweenable().tween({
8777 from: { posY: -40, opacity: 0, scale: 3 },
8778 to: { posY: 0, opacity: 1, scale: 1 },
8779 delay: 300,
8780 duration: 400,
8781 easing: 'easeInOutQuint',
8782 step: function (state) {
8783
8784 // tools
8785 G.VOM.$toolbarTR[0].style[G.CSStransformName] = 'translateY(' + (state.posY) + 'px) ';
8786 G.VOM.$toolbarTL[0].style[G.CSStransformName] = 'translateY(' + (state.posY) + 'px) ';
8787 G.VOM.$buttonLeft[0].style[G.CSStransformName] = 'translateX(' + (state.posY) + 'px) ';
8788 G.VOM.$buttonRight[0].style[G.CSStransformName] = 'translateX(' + (-state.posY) + 'px) ';
8789
8790 // gallery
8791 if( G.O.viewerGallery != 'none' ) {
8792 G.VOM.gallery.$elt.css({ opacity: state.opacity });
8793 G.VOM.gallery.$elt[0].style[G.CSStransformName] = 'scale('+state.scale+')';
8794 }
8795
8796 },
8797 finish: function() {
8798 G.VOM.viewerDisplayed = true;
8799 ViewerMediaPanX(0);
8800 ViewerSetEvents();
8801
8802 LightboxDisplay('');
8803
8804 if( G.O.slideshowAutoStart ) {
8805 G.VOM.playSlideshow = false;
8806 SlideshowToggle();
8807 }
8808
8809 ViewerToolsUnHide();
8810 LightboxDisplayFinalize('');
8811 }
8812 });
8813
8814
8815
8816
8817 // stop click propagation on media ==> if the user clicks outside of an media, the viewer is closed
8818 // --> no more supported since v2.0.0
8819 // G.VOM.$viewer.find('img').on('click', function (e) { e.stopPropagation(); });
8820
8821
8822 // ViewerMediaPanX(0);
8823 // ViewerSetEvents();
8824
8825 // LightboxDisplay('');
8826
8827 // if( G.O.slideshowAutoStart ) {
8828 // G.VOM.playSlideshow = false;
8829 // SlideshowToggle();
8830 // }
8831 }
8832
8833 function ViewerEvents() {
8834 if( !G.VOM.viewerDisplayed || G.VOM.viewerMediaIsChanged ) {
8835 // if( !G.VOM.viewerDisplayed || G.VOM.viewerMediaIsChanged || G.VOM.content.current.NGY2Item().mediaKind != 'img') {
8836 // ignore fired event if viewer not displayed or if currently changed (or if current media not an image)
8837 return false;
8838 }
8839 return true;
8840 }
8841
8842 // VIEWER - BUILD THE THUMBNAILS GALLERY
8843 function LightboxGalleryBuild() {
8844
8845 G.VOM.gallery.firstDisplay = true;
8846
8847 if( G.O.viewerGallery != 'none' ) {
8848
8849 var tw = G.O.viewerGalleryTWidth;
8850 var th = G.O.viewerGalleryTHeight;
8851 var gutter = 2;
8852
8853 var t = '';
8854 for( var i=0; i< G.VOM.items.length; i++) {
8855 var idx = G.VOM.items[i].ngy2ItemIdx;
8856 var o = G.I[idx];
8857 var src = (o.thumbImg().src).replace(/'/g, "%27"); // replace single quote with %27
8858 src = src.replace(/\\/g, '\\\\'); // single backslashes are replaced by double backslashes
8859 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>';
8860 }
8861 G.VOM.gallery.gwidth = (tw+2*gutter) * G.VOM.items.length;
8862 G.VOM.gallery.oneTmbWidth = tw+2*gutter;
8863 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>";
8864 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);
8865 G.VOM.gallery.$tmbCont = G.VOM.gallery.$elt.find('.nGY2VThumbnailContainer')
8866
8867 G.VOM.gallery.Resize();
8868 G.VOM.gallery.SetThumbnailActive();
8869
8870 }
8871 }
8872
8873
8874 // Lightbox gesture handling
8875 function ViewerSetEvents() {
8876
8877 if( G.VOM.hammertime == null ) {
8878
8879 G.VOM.hammertime = new NGHammer.Manager(G.VOM.$baseCont[0], {
8880 // domEvents: true,
8881 recognizers: [
8882 [NGHammer.Pinch, { enable: true }],
8883 [NGHammer.Pan, { direction: NGHammer.DIRECTION_ALL }]
8884 ]
8885 });
8886
8887 // PAN
8888 G.VOM.hammertime.on('pan', function(ev) {
8889 if( !ViewerEvents() ) { return; }
8890
8891
8892 if( G.VOM.panMode == 'off' ) {
8893 // PAN START -> determine the element to pan
8894 if( ev.target.dataset.ngy2_lightbox_thumbnail != undefined || ev.target.dataset.ngy2_lightbox_gallery != undefined ){
8895 G.VOM.panMode = 'gallery';
8896 }
8897 else {
8898 if( G.VOM.zoom.isZooming ) {
8899 G.VOM.panMode = 'zoom';
8900 }
8901 else {
8902 G.VOM.panMode = 'media';
8903 }
8904 }
8905 }
8906
8907 // PAN the determined element
8908 switch( G.VOM.panMode ) {
8909 case 'zoom':
8910 // pan zoomed image
8911 ViewerImagePanSetPosition(G.VOM.panPosX + ev.deltaX, G.VOM.panPosY + ev.deltaY, G.VOM.content.current.$media, false);
8912 G.VOM.toolsHide();
8913 break;
8914
8915 case 'media':
8916 if( Math.abs(ev.deltaY) > G.VOM.panThreshold && Math.abs(ev.deltaX) < G.VOM.panThreshold && !G.VOM.panXOnly ) {
8917 // pan viewer down/up to close the lightbox
8918 ViewerMediaPanX( 0 );
8919 var dist = 0;
8920 if( ev.deltaY < 0 ) {
8921 // pan up
8922 dist = Math.max( ev.deltaY, -200);
8923 }
8924 else {
8925 // pan down
8926 dist = Math.min( ev.deltaY, 200);
8927 }
8928 G.VOM.$viewer[0].style[G.CSStransformName] = 'translateY(' + dist + 'px) ';
8929 G.VOM.$viewer.css('opacity', 1-Math.abs(dist)/200/2);
8930 }
8931 else {
8932 // pan media left/right
8933 if( Math.abs(ev.deltaX) > G.VOM.panThreshold ) {
8934 G.VOM.panXOnly = true;
8935 }
8936 ViewerMediaPanX( ev.deltaX );
8937 G.VOM.$viewer[0].style[G.CSStransformName] = 'translateY(0px)';
8938 G.VOM.$viewer.css('opacity', 1);
8939 }
8940 break;
8941
8942 case 'gallery':
8943 G.VOM.gallery.PanGallery( ev.deltaX );
8944 break;
8945 }
8946
8947 });
8948
8949 // PAN END
8950 G.VOM.hammertime.on('panend', function(ev) {
8951 if( !ViewerEvents() ) { return; }
8952
8953 switch( G.VOM.panMode ) {
8954 case 'zoom':
8955 // PAN END in image zoom mode
8956 G.VOM.timeImgChanged = new Date().getTime();
8957 ViewerImagePanSetPosition( G.VOM.panPosX+ev.deltaX, G.VOM.panPosY+ev.deltaY, G.VOM.content.current.$media, true);
8958 break;
8959 case 'media':
8960 var panY = false;
8961 if( !G.VOM.panXOnly ) {
8962 if( Math.abs(ev.deltaY) > 50 && Math.abs(ev.deltaX) < 50 ) {
8963 // close viewer
8964 LightboxClose();
8965 panY = true;
8966 }
8967 }
8968 if( !panY ) {
8969 if( Math.abs( ev.deltaX ) < 50 ) {
8970 ViewerMediaPanX(0);
8971 }
8972 else {
8973 ev.deltaX > 50 ? DisplayPreviousMedia( Math.abs(ev.velocityX) ) : DisplayNextMedia( Math.abs(ev.velocityX) );
8974 }
8975 }
8976 G.VOM.panXOnly = false;
8977 break;
8978 case 'gallery':
8979 // PAN END on thumbnail gallery
8980 G.VOM.gallery.posX += ev.deltaX;
8981 G.VOM.gallery.PanGallery( 0 );
8982 G.VOM.gallery.PanGalleryEnd( ev.velocityX );
8983 break;
8984 }
8985
8986 G.VOM.panMode = 'off';
8987 });
8988
8989
8990 // ZOOM FEATURE ENABLED
8991 if( G.O.viewerZoom ) {
8992
8993 G.VOM.hammertime.add( new NGHammer.Tap({ event: 'doubletap', taps: 2, interval: 250 }) );
8994 G.VOM.hammertime.add( new NGHammer.Tap({ event: 'singletap' }) );
8995 G.VOM.hammertime.get('doubletap').recognizeWith('singletap');
8996 G.VOM.hammertime.get('singletap').requireFailure('doubletap');
8997
8998 // single tap -> next/previous media
8999 G.VOM.hammertime.on('singletap', function(ev) {
9000
9001 if( !ViewerEvents() ) { return; }
9002
9003 // Gallery on viewer -> click/touch on one thumbnail -> display corresponding image
9004 if( ev.target.dataset.ngy2_lightbox_thumbnail != undefined ){
9005
9006 var idx = parseInt(ev.target.dataset.ngy2_idx);
9007 var vidx = parseInt(ev.target.dataset.ngy2_vidx);
9008
9009 if( !isNaN(idx) && vidx != G.VOM.content.current.vIdx ) {
9010
9011 if( vidx > G.VOM.content.current.vIdx ) {
9012 TriggerCustomEvent('lightboxNextImage');
9013
9014 // replace the next media with selected media
9015 G.VOM.content.next.$media.empty();
9016 var nextItem = G.I[idx];
9017 G.VOM.content.next.vIdx = vidx;
9018 let spreloader = '<div class="nGY2ViewerMediaLoaderDisplayed"></div>';
9019 if( nextItem.mediaKind == 'img' && nextItem.imageWidth != 0 && nextItem.imageHeight != 0 ) {
9020 spreloader = '<div class="nGY2ViewerMediaLoaderHidden"></div>';
9021 }
9022 G.VOM.content.next.$media.append( spreloader + nextItem.mediaMarkup );
9023 if( nextItem.mediaKind == 'img' ) {
9024 G.VOM.ImageLoader.loadImage(VieweImgSizeRetrieved, nextItem);
9025 }
9026 else {
9027 ViewerMediaCenterNotImg( G.VOM.content.next.$media );
9028 }
9029 LightboxDisplay('nextImage');
9030
9031 }
9032 else {
9033 TriggerCustomEvent('lightboxPreviousImage');
9034
9035 // replace the previous media with selected media
9036 G.VOM.content.previous.$media.empty();
9037 var previousItem = G.I[idx];
9038 G.VOM.content.previous.vIdx = vidx;
9039 let spreloader = '<div class="nGY2ViewerMediaLoaderDisplayed"></div>';
9040 if( previousItem.mediaKind == 'img' && previousItem.imageWidth != 0 && previousItem.imageHeight != 0 ) {
9041 spreloader = '<div class="nGY2ViewerMediaLoaderHidden"></div>';
9042 }
9043 G.VOM.content.previous.$media.append( spreloader + previousItem.mediaMarkup );
9044 if( previousItem.mediaKind == 'img' ) {
9045 G.VOM.ImageLoader.loadImage(VieweImgSizeRetrieved, previousItem);
9046 }
9047 else {
9048 ViewerMediaCenterNotImg( G.VOM.content.previous.$media );
9049 }
9050 LightboxDisplay('previousImage');
9051 }
9052 return;
9053 }
9054 }
9055
9056
9057 StopPropagationPreventDefault(ev.srcEvent);
9058 if( G.VOM.toolbarsDisplayed == false ) {
9059 debounce( ViewerToolsUnHide, 100, false)();
9060 G.VOM.singletapTime = new Date().getTime();
9061 }
9062 else {
9063 // toolbars are displayed -> display next/previous media
9064 if( (new Date().getTime()) - G.VOM.singletapTime < 400 ) { return; } // to avoid conflict with MOUSEMOVE event
9065
9066 if( G.VOM.content.current.NGY2Item().mediaKind == 'img' && ev.target.className.indexOf('nGY2ViewerMedia') !== -1 ) {
9067 var x =0;
9068 if( ev.srcEvent instanceof MouseEvent ) {
9069 x = ev.srcEvent.pageX;
9070 }
9071 else {
9072 x = ev.srcEvent.changedTouches[0].pageX;
9073 }
9074 if( x < (G.GOM.cache.viewport.w/2) ) {
9075 DisplayPreviousMedia();
9076 }
9077 else {
9078 DisplayNextMedia();
9079 }
9080 }
9081 }
9082 });
9083
9084 // double tap -> zoom
9085 G.VOM.hammertime.on('doubletap', function(ev) {
9086 if( !ViewerEvents() ) { return; }
9087 StopPropagationPreventDefault(ev.srcEvent);
9088
9089 if( ev.target.className.indexOf('nGY2ViewerMedia') !== -1 ) {
9090 // double tap only on image
9091 if( G.VOM.zoom.isZooming ) {
9092 G.VOM.zoom.isZooming = false;
9093 // G.VOM.zoom.userFactor = 1;
9094 ResizeLightbox(true);
9095 }
9096 else {
9097 if( ViewerZoomStart() ) {
9098 G.VOM.zoom.userFactor = 1.5;
9099 ViewerMediaSetPosAndZoom();
9100 }
9101 }
9102 }
9103 });
9104
9105 // pinch end
9106 G.VOM.hammertime.on('pinchend', function(ev) {
9107 ev.srcEvent.stopPropagation();
9108 ev.srcEvent.preventDefault(); // cancel mouseenter event
9109 G.VOM.timeImgChanged = new Date().getTime();
9110 });
9111 G.VOM.hammertime.on('pinch', function(ev) {
9112 ev.srcEvent.stopPropagation();
9113 ev.srcEvent.preventDefault(); // cancel mouseenter event
9114
9115 if( ViewerZoomStart() ) {
9116 G.VOM.zoom.userFactor = ev.scale;
9117 ViewerZoomMax();
9118 ViewerZoomMin();
9119 ViewerMediaSetPosAndZoom(); // center media
9120 }
9121 });
9122 }
9123
9124
9125 else {
9126 // ZOOM FEATURE DISABLED
9127
9128 G.VOM.hammertime.add( new NGHammer.Tap({ event: 'singletap' }) );
9129
9130 // click/tap on image to go to next/previous one
9131 // G.VOM.hammertime.on('tap', function(ev) {
9132 G.VOM.hammertime.on('singletap', function(ev) {
9133 if( !ViewerEvents() ) { return; }
9134 StopPropagationPreventDefault( ev.srcEvent );
9135 if( G.VOM.toolbarsDisplayed == false ){
9136 // display tools on tap if hidden
9137 debounce( ViewerToolsUnHide, 100, false)();
9138 G.VOM.singletapTime = new Date().getTime();
9139 }
9140 else {
9141 // toolbars are displayed -> display next/previous media
9142 if( (new Date().getTime()) - G.VOM.singletapTime < 400 ) { return; } // to avoid conflict with MOUSEMOVE event
9143 if( ev.target.className.indexOf('nGY2ViewerMedia') !== -1 ) {
9144 var x = 0;
9145 if( ev.srcEvent instanceof MouseEvent ) {
9146 x = ev.srcEvent.pageX;
9147 }
9148 else {
9149 x = ev.srcEvent.changedTouches[0].pageX;
9150 }
9151 if( x < (G.GOM.cache.viewport.w/2) ) {
9152 DisplayPreviousMedia();
9153 }
9154 else {
9155 DisplayNextMedia();
9156 }
9157 }
9158 }
9159
9160 });
9161 }
9162 }
9163 }
9164
9165
9166 function StopPropagationPreventDefault(e) {
9167 e.stopPropagation();
9168 e.preventDefault();
9169 }
9170
9171 // Hide toolbars on user inactivity
9172 function ViewerToolsHide() {
9173 if( G.VOM.viewerDisplayed ) {
9174 G.VOM.toolbarsDisplayed = false;
9175 ViewerToolsOpacity(0);
9176 }
9177 }
9178
9179 function ViewerToolsUnHide() {
9180 if( G.VOM.viewerDisplayed ) {
9181 G.VOM.toolbarsDisplayed = true;
9182 ViewerToolsOpacity(1);
9183 G.VOM.toolsHide(); // re-init delay before hide tools+gallery
9184 }
9185 }
9186
9187 function ViewerToolsOpacity( op ) {
9188 if( G.VOM.$toolbar != null ) {
9189 G.VOM.$toolbar.css('opacity', op);
9190 }
9191 if( G.VOM.$toolbarTL != null ) {
9192 G.VOM.$toolbarTL.css('opacity', op);
9193 }
9194 if( G.VOM.$toolbarTR != null ) {
9195 G.VOM.$toolbarTR.css('opacity', op);
9196 }
9197
9198 // next/previous
9199 G.VOM.$content.find('.nGY2ViewerAreaNext').css('opacity', op);
9200 G.VOM.$content.find('.nGY2ViewerAreaPrevious').css('opacity', op);
9201
9202 // gallery
9203 // G.VOM.gallery.$elt.css('opacity', op);
9204 }
9205
9206
9207
9208 function ViewerToolsOn() {
9209 // removes all events
9210 G.VOM.$viewer.off('touchstart click', '.ngy2viewerToolAction', ViewerToolsAction);
9211
9212 // action button
9213 G.VOM.$viewer.on('touchstart click', '.ngy2viewerToolAction', ViewerToolsAction);
9214 }
9215
9216
9217 // Actions of the button/elements
9218 function ViewerToolsAction(e) {
9219 // delay to avoid twice handling on smartphone/tablet (both touchstart click events are fired)
9220 if( (new Date().getTime()) - G.timeLastTouchStart < 300 ) { return; }
9221 G.timeLastTouchStart = new Date().getTime();
9222
9223 var $this = $(this);
9224 var ngy2action = $this.data('ngy2action');
9225 if( ngy2action == undefined ) { return; }
9226 switch( ngy2action ) {
9227 case 'next':
9228 StopPropagationPreventDefault(e);
9229 DisplayNextMedia();
9230 break;
9231 case 'previous':
9232 StopPropagationPreventDefault(e);
9233 DisplayPreviousMedia();
9234 break;
9235 case 'playPause':
9236 e.stopPropagation();
9237 SlideshowToggle();
9238 break;
9239 case 'zoomIn':
9240 StopPropagationPreventDefault(e);
9241 if( ViewerZoomStart() ) { ViewerZoomIn( true ); }
9242 break;
9243 case 'zoomOut':
9244 StopPropagationPreventDefault(e);
9245 if( ViewerZoomStart() ) { ViewerZoomIn( false ); }
9246 break;
9247 case 'minimize':
9248 // toggle toolbar visibility
9249 StopPropagationPreventDefault(e);
9250 if( G.VOM.toolbarMode == 'std' ) {
9251 ViewerToolbarForVisibilityMin();
9252 }
9253 else {
9254 ViewerToolbarForVisibilityStd();
9255 }
9256 break;
9257 case 'fullScreen':
9258 // Toggle viewer fullscreen mode on/off
9259 e.stopPropagation();
9260 if( ngscreenfull.enabled ) {
9261 ngscreenfull.toggle();
9262 }
9263 break;
9264 case 'info':
9265 e.stopPropagation();
9266 ItemDisplayInfo( G.VOM.content.current.NGY2Item() );
9267 break;
9268 case 'close':
9269 StopPropagationPreventDefault(e);
9270 if( (new Date().getTime()) - G.VOM.timeImgChanged < 400 ) { return; }
9271 LightboxClose();
9272 break;
9273 case 'download':
9274 StopPropagationPreventDefault(e);
9275 DownloadImage(G.VOM.items[G.VOM.content.current.vIdx].ngy2ItemIdx);
9276 break;
9277 case 'share':
9278 StopPropagationPreventDefault(e);
9279 PopupShare(G.VOM.items[G.VOM.content.current.vIdx].ngy2ItemIdx);
9280 break;
9281 case 'linkOriginal':
9282 StopPropagationPreventDefault(e);
9283 OpenOriginal( G.VOM.content.current.NGY2Item() );
9284 break;
9285 case 'rotateLeft':
9286 StopPropagationPreventDefault(e);
9287 ViewerImageRotate(-90);
9288 break;
9289 case 'rotateRight':
9290 StopPropagationPreventDefault(e);
9291 ViewerImageRotate(90);
9292 break;
9293 case 'shoppingcart':
9294 StopPropagationPreventDefault(e);
9295 AddToCart( G.VOM.items[G.VOM.content.current.vIdx].ngy2ItemIdx, 'lightbox');
9296 break;
9297 }
9298
9299 // custom button
9300 var fu = G.O.fnImgToolbarCustClick;
9301 if( ngy2action.indexOf('custom') == 0 && fu !== null ) {
9302 typeof fu == 'function' ? fu(ngy2action, $this, G.VOM.content.current.NGY2Item() ) : window[fu](ngy2action, $this, G.VOM.content.current.NGY2Item() );
9303 }
9304 }
9305
9306 // rotate displayed image
9307 function ViewerImageRotate( angle ) {
9308 var item = G.VOM.content.current.NGY2Item();
9309 if( item.mediaKind == 'img' ) {
9310 item.rotationAngle += angle;
9311 item.rotationAngle = item.rotationAngle % 360;
9312 if( item.rotationAngle < 0 ) {
9313 item.rotationAngle += 360;
9314 }
9315 ViewerMediaPanX( 0 );
9316 ViewerMediaSetPosAndZoomOne( G.VOM.content.current, true );
9317 }
9318 }
9319
9320
9321 // Display photo infos in popup/modal
9322 function ItemDisplayInfo( ng2item ) {
9323
9324 var content = '<div class="nGY2PopupOneItem">' + ng2item.title + '</div>';
9325 content += '<div class="nGY2PopupOneItemText">' + ng2item.description + '</div>';
9326 if( ng2item.author != '' ) {
9327 content += '<div class="nGY2PopupOneItemText">' + G.O.icons.user + ' ' + ng2item.author + '</div>';
9328 }
9329 if( ng2item.exif.model != '' ) {
9330 content += '<div class="nGY2PopupOneItemText">' + G.O.icons.config + ' ' + ng2item.exif.model + '</div>';
9331 }
9332 var sexif = G.O.icons.picture + ':';
9333 if( ng2item.exif.flash != '' || ng2item.exif.focallength != '' || ng2item.exif.fstop != '' || ng2item.exif.exposure != '' || ng2item.exif.iso != '' || ng2item.exif.time != '' ) {
9334 sexif += '<br>';
9335 sexif += ng2item.exif.flash == '' ? '' : ' &nbsp; ' + ng2item.exif.flash;
9336 sexif += ng2item.exif.focallength == '' ? '' : ' &nbsp; ' + ng2item.exif.focallength+'mm';
9337 sexif += ng2item.exif.fstop == '' ? '' : ' &nbsp; f' + ng2item.exif.fstop;
9338 sexif += ng2item.exif.exposure == '' ? '' : ' &nbsp; ' + ng2item.exif.exposure+'s';
9339 sexif += ng2item.exif.iso == '' ? '' : ' &nbsp; ' + ng2item.exif.iso+' ISO';
9340 if( ng2item.exif.time != '' ) {
9341 // var date = new Date(parseInt(ng2item.exif.time));
9342 // sexif += ' &nbsp; '+date.toLocaleDateString();
9343 sexif += ' &nbsp; ' + ng2item.exif.time;
9344 }
9345 }
9346 else {
9347 sexif += ' n/a';
9348 }
9349 content += '<div class="nGY2PopupOneItemText">' + sexif + '</div>';
9350
9351 if( ng2item.exif.location != '' ) {
9352 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>';
9353 // embed google map in iframe (no api key required)
9354 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>';
9355 }
9356 else {
9357 content += '<div class="nGY2PopupOneItemText">' + G.O.icons.location + ': n/a</div>';
9358 }
9359
9360 var r = { title: G.O.icons.viewerInfo, content: content };
9361
9362 // callback
9363 var fu = G.O.fnPopupMediaInfo;
9364 if( fu !== null ) {
9365 typeof fu == 'function' ? r=fu(ng2item, r.title, r.content) : r=window[fu](ng2item, r.title, r.content);
9366 }
9367
9368
9369 Popup( r.title, r.content, 'Left');
9370
9371 }
9372
9373
9374
9375 function ToolbarAddElt( elt ) {
9376 var r = '<div class="ngbt ngy2viewerToolAction ',
9377 e=elt.replace(/^\s+|\s+$/g, ''); // remove trailing/leading whitespace
9378 switch( e ) {
9379 case 'minimizeButton':
9380 case 'minimize':
9381 var ic = G.O.icons.viewerToolbarMin;
9382 if( G.VOM.toolbarMode == 'min' ) {
9383 ic = G.O.icons.viewerToolbarStd;
9384 }
9385 r += 'minimizeButton nGEvent" data-ngy2action="minimize">'+ic+'</div>';
9386 break;
9387 case 'previousButton':
9388 case 'previous':
9389 r += 'previousButton nGEvent" data-ngy2action="previous">'+ G.O.icons.viewerPrevious +'</div>';
9390 break;
9391 case 'pageCounter':
9392 r += 'pageCounter nGEvent"></div>';
9393 break;
9394 case 'nextButton':
9395 case 'next':
9396 r += 'nextButton nGEvent" data-ngy2action="next">'+ G.O.icons.viewerNext +'</div>';
9397 break;
9398 case 'playPauseButton':
9399 case 'playPause':
9400 r += 'playButton playPauseButton nGEvent" data-ngy2action="playPause">'+ G.O.icons.viewerPlay +'</div>';
9401 break;
9402 case 'rotateLeft':
9403 r += 'rotateLeftButton nGEvent" data-ngy2action="rotateLeft">'+ G.O.icons.viewerRotateLeft +'</div>';
9404 break;
9405 case 'rotateRight':
9406 r += 'rotateRightButton nGEvent" data-ngy2action="rotateRight">'+ G.O.icons.viewerRotateRight +'</div>';
9407 break;
9408 case 'downloadButton':
9409 case 'download':
9410 r += 'downloadButton nGEvent" data-ngy2action="download">'+ G.O.icons.viewerDownload +'</div>';
9411 break;
9412 case 'zoomButton':
9413 case 'zoom':
9414 r += 'nGEvent" data-ngy2action="zoomIn">'+ G.O.icons.viewerZoomIn +'</div><div class="ngbt ngy2viewerToolAction nGEvent" data-ngy2action="zoomOut">'+ G.O.icons.viewerZoomOut +'</div>';
9415 break;
9416 case 'fullscreenButton':
9417 case 'fullscreen':
9418 var s = G.O.icons.viewerFullscreenOn;
9419 if( ngscreenfull.enabled && G.VOM.viewerIsFullscreen ) {
9420 s = G.O.icons.viewerFullscreenOff;
9421 }
9422 r += 'setFullscreenButton fullscreenButton nGEvent" data-ngy2action="fullScreen">'+s+'</div>';
9423 break;
9424 case 'infoButton':
9425 case 'info':
9426 r += 'infoButton nGEvent" data-ngy2action="info">'+ G.O.icons.viewerInfo +'</div>';
9427 break;
9428 case 'linkOriginalButton':
9429 case 'linkOriginal':
9430 r += 'linkOriginalButton nGEvent" data-ngy2action="linkOriginal">' + G.O.icons.viewerLinkOriginal + '</div>';
9431 break;
9432 case 'closeButton':
9433 case 'close':
9434 r += 'closeButton nGEvent" data-ngy2action="close">'+ G.O.icons.buttonClose +'</div>';
9435 break;
9436 case 'shareButton':
9437 case 'share':
9438 r += 'nGEvent" data-ngy2action="share">'+ G.O.icons.viewerShare +'</div>';
9439 break;
9440 case 'label':
9441 r += '"><div class="label"><div class="title nGEvent" itemprop="name"></div><div class="description nGEvent" itemprop="description"></div></div></div>';
9442 break;
9443 case 'shoppingcart':
9444 r += 'closeButton nGEvent" data-ngy2action="shoppingcart">'+ G.O.icons.viewerShoppingcart +'</div>';
9445 break;
9446 default:
9447 // custom button
9448 if( e.indexOf('custom') == 0 ) {
9449 var t = '';
9450 // content to display from custom script
9451 var fu = G.O.fnImgToolbarCustInit;
9452 if( fu !== null ) {
9453 typeof fu == 'function' ? fu(e) : window[fu](e);
9454 }
9455 if( t == undefined || t == '' ) {
9456 // content from icons
9457 var n = e.substring(6);
9458 t = G.O.icons['viewerCustomTool'+n];
9459 }
9460 r += 'ngy2CustomBtn ' + e + ' nGEvent" data-ngy2action="' + e + '">' + t + '</div>';
9461 }
9462 else {
9463 r = '';
9464 }
9465 break;
9466 }
9467 return r;
9468 }
9469
9470
9471 // toggle slideshow mode on/off
9472 function SlideshowToggle(){
9473 if( G.VOM.playSlideshow ) {
9474 window.clearTimeout(G.VOM.playSlideshowTimerID);
9475 G.VOM.playSlideshow = false;
9476 G.VOM.$viewer.find('.playPauseButton').html(G.O.icons.viewerPlay);
9477 }
9478 else {
9479 G.VOM.playSlideshow = true;
9480 DisplayNextMedia();
9481 G.VOM.$viewer.find('.playPauseButton').html(G.O.icons.viewerPause);
9482 }
9483 }
9484
9485 function ViewerToolbarForVisibilityStd() {
9486 G.VOM.toolbarMode = 'std';
9487
9488 var sTB = '';
9489 var t = G.O.viewerToolbar.standard.split(',');
9490 for( var i = 0, lt = t.length; i < lt; i++) {
9491 sTB += ToolbarAddElt( t[i] );
9492 }
9493 G.VOM.$toolbar.find('.toolbar').html(sTB);
9494 ViewerToolbarElementContent();
9495 }
9496
9497 function ViewerToolbarForVisibilityMin() {
9498 if( G.O.viewerToolbar.minimized == undefined || G.O.viewerToolbar.minimized == '' ) {
9499 ViewerToolbarForVisibilityStd();
9500 }
9501 else {
9502 G.VOM.toolbarMode = 'min';
9503 var sTB = '';
9504 var t = G.O.viewerToolbar.minimized.split(',');
9505 for( var i = 0, lt = t.length; i < lt; i++) {
9506 sTB += ToolbarAddElt( t[i] );
9507 }
9508 G.VOM.$toolbar.find('.toolbar').html(sTB);
9509 ViewerToolbarElementContent();
9510 }
9511 }
9512
9513 function ViewerToolbarElementContent() {
9514
9515 var vomIdx = G.VOM.content.current.vIdx;
9516 if( vomIdx == null ) { return; }
9517
9518 var item = G.VOM.content.current.NGY2Item();
9519
9520 // LABEL
9521 var setTxt = false;
9522 // set title
9523 if( item.title !== undefined && item.title != '' ) {
9524 G.VOM.$viewer.find('.ngy2viewerToolAction').find('.title').html(item.title);
9525 setTxt = true;
9526 }
9527 else {
9528 G.VOM.$viewer.find('.ngy2viewerToolAction').find('.title').html('');
9529 }
9530 // set description
9531 if( item.description !== undefined && item.description != '' ) {
9532 G.VOM.$viewer.find('.ngy2viewerToolAction').find('.description').html(item.description);
9533 setTxt = true;
9534 }
9535 else {
9536 G.VOM.$viewer.find('.ngy2viewerToolAction').find('.description').html('');
9537 }
9538
9539 if( setTxt ) {
9540 G.VOM.$viewer.find('.ngy2viewerToolAction').find('.label').show();
9541 }
9542 else {
9543 G.VOM.$viewer.find('.ngy2viewerToolAction').find('.label').hide();
9544 }
9545
9546 // set page number
9547 var viewerMaxImages = G.VOM.items.length;
9548 if( viewerMaxImages > 0 ) {
9549 G.VOM.$viewer.find('.pageCounter').html((G.VOM.items[vomIdx].mediaNumber)+'/'+viewerMaxImages);
9550 }
9551
9552 // custom elements
9553 var $cu = G.VOM.$viewer.find('.ngy2CustomBtn');
9554 var fu = G.O.fnImgToolbarCustDisplay;
9555 if( $cu.length > 0 && fu !== null ) {
9556 typeof fu == 'function' ? fu($cu, item) : window[fu]($cu, item);
9557 }
9558
9559 // set event handlers again
9560 ViewerToolsOn();
9561 }
9562
9563 // Pan the media container in the lightbox (left/right)
9564 function ViewerMediaPanX( posX ) {
9565 G.VOM.swipePosX = posX;
9566 if( G.CSStransformName == null ) {
9567 // no pan if CSS transform not supported
9568 // G.VOM.$mediaCurrent.css({ left: posX });
9569 }
9570 else {
9571
9572 // pan left/right the current media
9573 // window.ng_draf( function() {
9574 G.VOM.content.current.$media[0].style[G.CSStransformName] = 'translate(' + posX + 'px, 0px)';
9575 // });
9576
9577 var itemPrevious = G.VOM.content.previous.NGY2Item();
9578 var itemNext = G.VOM.content.next.NGY2Item();
9579
9580 // next/previous media
9581 if( G.O.imageTransition.startsWith('SWIPE') ) {
9582 if( itemPrevious.mediaTransition() ) {
9583 ViewerSetMediaVisibility(G.VOM.content.previous, 1);
9584 }
9585 if( itemNext.mediaTransition() ) {
9586 ViewerSetMediaVisibility(G.VOM.content.next, 1);
9587 }
9588
9589 var sc = Math.min( Math.max( Math.abs(posX) / G.VOM.window.lastWidth, .8), 1);
9590 if( G.O.imageTransition == 'SWIPE' ) { sc = 1; }
9591
9592 if( posX > 0 ) {
9593 let dir = G.VOM.window.lastWidth;
9594 if( itemPrevious.mediaTransition() ) {
9595 // window.ng_draf( function() {
9596 G.VOM.content.previous.$media[0].style[G.CSStransformName] = 'translate(' + (-dir + posX) + 'px, 0px) scale(' + sc + ')';
9597 // });
9598 }
9599 if( itemNext.mediaTransition() ) {
9600 // window.ng_draf( function() {
9601 G.VOM.content.next.$media[0].style[G.CSStransformName] = 'translate(' + (dir) + 'px, 0px) scale(' + sc + ')';
9602 // });
9603 }
9604 }
9605 else {
9606 let dir = -G.VOM.window.lastWidth;
9607 if( itemNext.mediaTransition() ) {
9608 // window.ng_draf( function() {
9609 G.VOM.content.next.$media[0].style[G.CSStransformName] = 'translate(' + (-dir + posX) + 'px, 0px) scale(' + sc + ')';
9610 // });
9611 }
9612 if( itemPrevious.mediaTransition() ) {
9613 // window.ng_draf( function() {
9614 G.VOM.content.previous.$media[0].style[G.CSStransformName] = 'translate(' + (dir) + 'px, 0px) scale(' + sc + ')';
9615 // });
9616 }
9617 }
9618 }
9619
9620
9621 if( G.O.imageTransition == 'SLIDEAPPEAR' ) {
9622 G.VOM.content.previous.$media[0].style[G.CSStransformName] = '';
9623 G.VOM.content.next.$media[0].style[G.CSStransformName] = '';
9624 if( posX < 0 ) {
9625 let o = (-posX) / G.VOM.window.lastWidth;
9626 if( itemNext.mediaTransition() ) {
9627 ViewerSetMediaVisibility(G.VOM.content.next, o);
9628 }
9629 if( itemPrevious.mediaTransition() ) {
9630 ViewerSetMediaVisibility(G.VOM.content.previous, 0);
9631 }
9632 }
9633 else {
9634 let o = posX / G.VOM.window.lastWidth;
9635 if( itemPrevious.mediaTransition() ) {
9636 ViewerSetMediaVisibility(G.VOM.content.previous, o);
9637 }
9638 if( itemNext.mediaTransition() ) {
9639 ViewerSetMediaVisibility(G.VOM.content.next, 0);
9640 }
9641 }
9642 }
9643 }
9644 }
9645
9646 // Display next image
9647 function DisplayNextMedia( velocity ) {
9648 velocity = velocity || 0;
9649
9650 if( G.VOM.viewerMediaIsChanged || ((new Date().getTime()) - G.VOM.timeImgChanged < 300) ) { return; }
9651
9652 TriggerCustomEvent('lightboxNextImage');
9653 LightboxDisplay('nextImage', velocity);
9654 };
9655
9656 // Display previous image
9657 function DisplayPreviousMedia( velocity ) {
9658 velocity = velocity || 0;
9659
9660 if( G.VOM.viewerMediaIsChanged || ((new Date().getTime()) - G.VOM.timeImgChanged < 300) ) { return; }
9661 if( G.VOM.playSlideshow ) {
9662 SlideshowToggle();
9663 }
9664
9665 TriggerCustomEvent('lightboxPreviousImage');
9666 LightboxDisplay( 'previousImage', velocity);
9667 };
9668
9669
9670
9671 // Display image (with animation if possible)
9672 function LightboxDisplay( displayType, velocity ) {
9673
9674 velocity = velocity || 0;
9675
9676 if( G.O.debugMode && console.timeline ) { console.timeline('nanogallery2_viewer'); }
9677
9678 if( G.VOM.playSlideshow ) { window.clearTimeout( G.VOM.playSlideshowTimerID ); }
9679
9680 var current_content_item = null;
9681 var new_content_item = null;
9682
9683 G.VOM.timeImgChanged = new Date().getTime();
9684 G.VOM.viewerMediaIsChanged = true;
9685 G.VOM.zoom.isZooming = false;
9686 ResizeLightbox(true);
9687
9688 switch( displayType ) {
9689 case '':
9690 current_content_item = G.VOM.content.current;
9691 new_content_item = G.VOM.content.current;
9692 break;
9693 case 'previousImage':
9694 current_content_item = G.VOM.content.current;
9695 new_content_item = G.VOM.content.previous;
9696 break;
9697 default:
9698 current_content_item = G.VOM.content.current;
9699 new_content_item = G.VOM.content.next;
9700 }
9701
9702 // SetLocationHash( next_ng2item.albumID, next_ng2item.GetID() );
9703 SetLocationHash( new_content_item.NGY2Item().albumID, new_content_item.NGY2Item().GetID() );
9704
9705 if( displayType == '' ) {
9706 // first media -> no transition -> exit
9707 return;
9708 }
9709
9710 // animation duration is proportional of the remaining distance
9711 var vP = G.GOM.cache.viewport;
9712 var t_easing = '';
9713 var t_dur = 500 * (vP.w - Math.abs(G.VOM.swipePosX)) / vP.w;
9714 if( velocity > 0 ) {
9715 // velocity = pixels/millisecond
9716 t_dur = Math.min( (vP.w - Math.abs(G.VOM.swipePosX)) / velocity, t_dur);
9717 t_easing = 'linear'; // use linear to avoid a slow-down in the transition after user swipe
9718 }
9719
9720
9721 // animate the image transition between 2 medias
9722
9723 if( G.CSStransformName == null ) {
9724 // no CSS transform support -> no animation
9725 ViewerSetMediaVisibility(new_content_item, 1);
9726 ViewerSetMediaVisibility(current_content_item, 1);
9727 LightboxDisplayFinalize(displayType);
9728 }
9729 else {
9730 switch( G.O.imageTransition ) {
9731 case 'SWIPE':
9732 case 'SWIPE2':
9733 var dir = ( displayType == 'nextImage' ? - vP.w : vP.w );
9734 new_content_item.$media[0].style[G.CSStransformName] = 'translate(' + (-dir) + 'px, 0px) '
9735
9736 if( velocity == 0 ) {
9737 t_easing = G.O.imageTransition == 'swipe' ? 'easeInOutSine' : 'easeOutCubic';
9738 }
9739
9740 ViewerSetMediaVisibility(G.VOM.content.current, 1);
9741 G.VOM.content.current.$media[0].style[G.CSStransformName] = 'translate(0px, 0px)';
9742 ViewerSetMediaVisibility(new_content_item, 1);
9743
9744 new NGTweenable().tween({
9745 from: { t: G.VOM.swipePosX },
9746 to: { t: (displayType == 'nextImage' ? - vP.w : vP.w) },
9747 attachment: { dT: displayType, new_content_item: new_content_item, dir: dir, media_transition: new_content_item.NGY2Item().mediaTransition()},
9748 // delay: 30,
9749 duration: t_dur,
9750 easing: ( t_easing ),
9751 step: function (state, att) {
9752 // current displayed media
9753 G.VOM.content.current.$media[0].style[G.CSStransformName] = 'translate(' + state.t + 'px, 0px)';
9754
9755 // new media
9756 if( att.media_transition ) {
9757 // new media supports transition
9758 var sc = Math.min( Math.max( (Math.abs(state.t)) / G.VOM.window.lastWidth, .8), 1);
9759 if( G.O.imageTransition == 'SWIPE' ) { sc = 1; }
9760 att.new_content_item.$media[0].style[G.CSStransformName] = 'translate(' + (-att.dir+state.t) + 'px, 0px) scale(' + sc + ')';
9761 }
9762 },
9763 finish: function (state, att) {
9764 G.VOM.content.current.$media[0].style[G.CSStransformName] = '';
9765 ViewerSetMediaVisibility(G.VOM.content.current, 0);
9766 att.new_content_item.$media[0].style[G.CSStransformName] = '';
9767 LightboxDisplayFinalize(att.dT);
9768 }
9769 });
9770 break;
9771
9772 case 'SLIDEAPPEAR':
9773 default:
9774 // var dir=(displayType == 'nextImage' ? - vP.w : vP.w);
9775 var op = (Math.abs(G.VOM.swipePosX)) / G.VOM.window.lastWidth;
9776 new_content_item.$media[0].style[G.CSStransformName] = '';
9777 if( velocity == 0 ) {
9778 t_easing ='easeInOutSine';
9779 }
9780 new NGTweenable().tween({
9781 from: { o: op, t: G.VOM.swipePosX },
9782 to: { o: 1, t: (displayType == 'nextImage' ? - vP.w : vP.w) },
9783 attachment: { dT: displayType, new_content_item:new_content_item, media_transition: new_content_item.NGY2Item().mediaTransition() },
9784 delay: 30,
9785 duration: t_dur,
9786 easing: t_easing,
9787 step: function (state, att) {
9788 // current media - translate
9789 G.VOM.content.current.$media[0].style[G.CSStransformName]= 'translate('+state.t+'px, 0px)';
9790
9791 // new media - opacity
9792 if( att.media_transition ) {
9793 // new media supports transition
9794 ViewerSetMediaVisibility(att.new_content_item, state.o);
9795 }
9796 },
9797 finish: function (state, att) {
9798 G.VOM.content.current.$media[0].style[G.CSStransformName]= '';
9799 LightboxDisplayFinalize(att.dT);
9800 }
9801 });
9802 break;
9803 }
9804 }
9805
9806 }
9807
9808
9809 function LightboxDisplayFinalize( displayType ) {
9810
9811 var newVomIdx = 0;
9812 switch( displayType ) {
9813 case '':
9814 // first media to display in lightbox
9815 newVomIdx = G.VOM.content.current.vIdx;
9816 break;
9817 case 'previousImage':
9818 // previous media
9819 newVomIdx = G.VOM.content.previous.vIdx;
9820 break;
9821 default:
9822 // next media
9823 newVomIdx = G.VOM.content.next.vIdx;
9824 }
9825
9826
9827
9828 G.VOM.content.current.vIdx = newVomIdx;
9829 G.VOM.content.next.vIdx = G.VOM.IdxNext();
9830 G.VOM.content.previous.vIdx = G.VOM.IdxPrevious();
9831 G.VOM.gallery.Resize();
9832 G.VOM.gallery.SetThumbnailActive();
9833
9834 var ngy2item = G.VOM.content.current.NGY2Item();
9835
9836 ViewerToolbarElementContent();
9837 if( G.O.debugMode && console.timeline ) { console.timelineEnd('nanogallery2_viewer'); }
9838
9839 var fu=G.O.fnImgDisplayed;
9840 if( fu !== null ) {
9841 typeof fu == 'function' ? fu(ngy2item) : window[fu](ngy2item);
9842 }
9843
9844 G.VOM.swipePosX = 0;
9845 if( displayType != '' ) {
9846 // not on first media display
9847 G.VOM.content.current.$media.removeClass('imgCurrent');
9848
9849 var $tmp = G.VOM.content.current.$media;
9850 switch( displayType ) {
9851 case 'nextImage':
9852 G.VOM.content.current.$media = G.VOM.content.next.$media;
9853 G.VOM.content.next.$media = $tmp;
9854 break;
9855 case 'previousImage':
9856 G.VOM.content.current.$media = G.VOM.content.previous.$media;
9857 G.VOM.content.previous.$media = $tmp;
9858 break;
9859 }
9860 }
9861
9862 G.VOM.content.current.$media.addClass('imgCurrent');
9863
9864 // re-sort the media containers --> current on top
9865 var $pans = G.VOM.$content.find('.nGY2ViewerMediaPan');
9866 G.VOM.content.current.$media.insertAfter($pans.last());
9867
9868 if( ngy2item.mediaKind == 'img' && ngy2item.imageWidth == 0 ) {
9869 ViewerSetMediaVisibility(G.VOM.content.current, 0);
9870 }
9871 else {
9872 G.VOM.content.current.$media.children().eq(0).attr('class', 'nGY2ViewerMediaLoaderHidden'); // hide preloader
9873 ViewerSetMediaVisibility(G.VOM.content.current, 1);
9874 }
9875
9876
9877 // set the new NEXT media
9878 G.VOM.content.next.$media.empty();
9879 var nextItem = G.VOM.content.next.NGY2Item();
9880 var spreloader = '<div class="nGY2ViewerMediaLoaderDisplayed"></div>';
9881 if( nextItem.mediaKind == 'img' && nextItem.imageWidth != 0 && nextItem.imageHeight != 0 ) {
9882 spreloader = '<div class="nGY2ViewerMediaLoaderHidden"></div>';
9883 }
9884 G.VOM.content.next.$media.append( spreloader + nextItem.mediaMarkup );
9885 ViewerSetMediaVisibility(G.VOM.content.next, 0);
9886 ViewerSetMediaVisibility(G.VOM.content.previous, 0);
9887 if( nextItem.mediaKind == 'img' ) {
9888 G.VOM.ImageLoader.loadImage(VieweImgSizeRetrieved, nextItem);
9889 }
9890 else {
9891 ViewerMediaCenterNotImg( G.VOM.content.next.$media );
9892 }
9893
9894 // set the new PREVIOUS media
9895 G.VOM.content.previous.$media.empty();
9896 var previousItem = G.VOM.content.previous.NGY2Item();
9897 spreloader = '<div class="nGY2ViewerMediaLoaderDisplayed"></div>';
9898 if( previousItem.mediaKind == 'img' && previousItem.imageWidth != 0 && previousItem.imageHeight != 0 ) {
9899 spreloader = '<div class="nGY2ViewerMediaLoaderHidden"></div>';
9900 }
9901 G.VOM.content.previous.$media.append( spreloader + previousItem.mediaMarkup );
9902 ViewerSetMediaVisibility(G.VOM.content.previous, 0);
9903 ViewerSetMediaVisibility(G.VOM.content.next, 0);
9904 if( previousItem.mediaKind == 'img' ) {
9905 G.VOM.ImageLoader.loadImage( VieweImgSizeRetrieved, previousItem );
9906 }
9907 else {
9908 ViewerMediaCenterNotImg( G.VOM.content.previous.$media );
9909 }
9910
9911
9912 // slideshow mode - wait until image is loaded to start the delay for next image
9913 if( G.VOM.playSlideshow ) {
9914 G.VOM.content.current.$media.children().eq(1).ngimagesLoaded().always( function( instance ) {
9915 if( G.VOM.playSlideshow ) {
9916 // in the meantime the user could have stopped the slideshow
9917 G.VOM.playSlideshowTimerID = window.setTimeout( function(){ DisplayNextMedia(); }, G.VOM.slideshowDelay );
9918 }
9919 });
9920 }
9921
9922 // close viewer when user clicks outside of the image
9923 // G.VOM.$mediaCurrent.on("click",function(e){
9924 // e.stopPropagation();
9925 // if( (new Date().getTime()) - G.VOM.timeImgChanged < 400 ) { return; }
9926 // StopPropagationPreventDefault(e);
9927 // LightboxClose(G.VOM.currItemIdx);
9928 // return false;
9929 // });
9930
9931 ResizeLightbox();
9932
9933 G.VOM.viewerMediaIsChanged = false;
9934 TriggerCustomEvent('lightboxImageDisplayed');
9935
9936 }
9937
9938
9939 // Is fired as soon as the size of an image has been retrieved (the download may not be finished)
9940 function VieweImgSizeRetrieved(w, h, item, n) {
9941
9942 item.imageWidth = w;
9943 item.imageHeight = h;
9944
9945 // image sized retrieved for currently displayed media
9946 if( G.VOM.content.current.NGY2Item() == item ) {
9947 // it is the current displayed media
9948 G.VOM.content.current.$media.children().eq(0).attr('class', 'nGY2ViewerMediaLoaderHidden'); // hide preloader
9949 ViewerSetMediaVisibility(G.VOM.content.current, 1);
9950 G.VOM.zoom.userFactor = 1;
9951 }
9952
9953 if( G.VOM.content.next.NGY2Item() == item ) { // next media
9954 G.VOM.content.next.$media.children().eq(0).attr('class', 'nGY2ViewerMediaLoaderHidden'); // hide preloader
9955 }
9956 if( G.VOM.content.previous.NGY2Item() == item ) { // previous media
9957 G.VOM.content.previous.$media.children().eq(0).attr('class', 'nGY2ViewerMediaLoaderHidden'); // hide preloader
9958 }
9959
9960 ViewerMediaSetPosAndZoom();
9961
9962 }
9963
9964 // Viewer - Set the visibility of the media and it's container
9965 // function ViewerSetMediaVisibility(item, $media, opacity ) {
9966 function ViewerSetMediaVisibility( content_item, opacity ) {
9967
9968 var item = content_item.NGY2Item();
9969 var $media = content_item.$media;
9970
9971 if( item.mediaKind == 'img' && item.imageWidth == 0 ) {
9972 // do not display image if width is unknown (--> callback will set the width when know)
9973 // setting opacity to 0 is not enough -> it is mandatory to change also the visibility to hidden to avoid responds to events (click/touch)
9974 // $media.children().css({ opacity: 0, visibility: 'hidden' });
9975 $media.children().eq(1).css({ opacity: 0, visibility: 'hidden' }); // hide media
9976 // $media.css({ opacity: 0, visibility: 'hidden' });
9977 return;
9978 }
9979
9980 if( opacity == 0 ) {
9981 // setting opacity to 0 is not enough -> it is mandatory to change also the visibility to hidden to avoid responds to events (click/touch)
9982 // $media.css({ opacity: 0, visibility: 'hidden' });
9983 $media.children().css({ opacity: 0, visibility: 'hidden' }); // hide media
9984 }
9985 else {
9986 // $media.css({ opacity: opacity, visibility: 'visible' });
9987 $media.children().css({ opacity: opacity, visibility: 'visible' }); // display media
9988 }
9989 }
9990
9991
9992 // Close the internal lightbox
9993 function LightboxClose( vomIdx ) {
9994
9995
9996 if( vomIdx == undefined ) {
9997 vomIdx = G.VOM.content.current.vIdx;
9998 }
9999
10000 G.VOM.viewerMediaIsChanged = false;
10001
10002 if( G.VOM.viewerDisplayed ) {
10003
10004 // set scrollbar visible again
10005 jQuery("body").removeClass("nGY2_body_scrollbar");
10006 jQuery("#nGY2_body_scrollbar_style").remove();
10007
10008 if( G.VOM.playSlideshow ) {
10009 window.clearTimeout( G.VOM.playSlideshowTimerID );
10010 G.VOM.playSlideshow = false;
10011 }
10012
10013 G.VOM.hammertime.destroy();
10014 G.VOM.hammertime = null;
10015
10016 if( ngscreenfull.enabled && G.VOM.viewerIsFullscreen ) {
10017 G.VOM.viewerIsFullscreen = false;
10018 ngscreenfull.exit();
10019 }
10020
10021 // G.VOM.$baseCont.hide(0).off().show(0).html('').remove();
10022 // G.VOM.$baseCont.remove(); // does not work... (?)
10023 jQuery('.nGY2ViewerContainer').remove();
10024 G.VOM.$baseCont = null;
10025 G.VOM.viewerDisplayed = false;
10026
10027 if( G.O.lightboxStandalone ) { return; }
10028
10029 if( G.O.thumbnailAlbumDisplayImage ) {
10030 // content of album displayed directly in lightbox (no gallery display for album content)
10031 if( vomIdx == null ) {
10032 // lightbox closed with browser back-button
10033 // the gallery is already displayed
10034 }
10035 else {
10036 var item = G.I[G.VOM.items[vomIdx].ngy2ItemIdx];
10037 var parent = NGY2Item.Get(G, item.albumID);
10038 if( G.GOM.albumIdx != parent.albumID ) {
10039 // display album only if not already displayed
10040 DisplayAlbum('-1', parent.albumID);
10041 }
10042 else {
10043 GalleryResize();
10044 SetLocationHash( '', '' );
10045 ThumbnailHoverReInitAll();
10046 }
10047 }
10048 // DisplayAlbum( '-', G.I[G.VOM.items[vomIdx].ngy2ItemIdx].albumID );
10049 }
10050 else {
10051 if( vomIdx != null ) {
10052 if( G.GOM.albumIdx == -1 ) {
10053 // album not displayed --> display gallery
10054 DisplayAlbum( '', G.I[G.VOM.items[vomIdx].ngy2ItemIdx].albumID );
10055 }
10056 else {
10057 GalleryResize();
10058 SetLocationHash( G.I[G.VOM.items[vomIdx].ngy2ItemIdx].albumID, '' );
10059 ThumbnailHoverReInitAll();
10060 }
10061 }
10062 }
10063 G.VOM.timeImgChanged = new Date().getTime();
10064 }
10065 }
10066
10067
10068 // Lightbox resized -> reposition elements
10069 function ResizeLightbox( forceUpdate ) {
10070 forceUpdate = typeof forceUpdate !== 'undefined' ? forceUpdate : false;
10071
10072 if( G.VOM.$toolbar === null ) { return; } // viewer build not finished
10073
10074
10075 // window.requestAnimationFrame( function() { // synchronize with screen
10076 var windowsW = G.VOM.$viewer.width();
10077 var windowsH = G.VOM.$viewer.height();
10078 var $elt = G.VOM.content.current.$media.children().eq(1);
10079 if( $elt == null || G.VOM.content.current.vIdx == -1 ) { return; }
10080
10081 if( !forceUpdate && G.VOM.window.lastWidth == windowsW && G.VOM.window.lastHeight == windowsH ) { return; }
10082
10083 G.VOM.window.lastWidth = windowsW;
10084 G.VOM.window.lastHeight = windowsH;
10085
10086 // var $tb = G.VOM.$toolbar.find('.toolbar');
10087 // var tb_OHt = $tb.outerHeight(true);
10088
10089
10090 var galleryHeight = 0;
10091 var cBottom = 0;
10092 // Height of the thumbnails gallery
10093 if( G.O.viewerGallery != 'none' ) {
10094 galleryHeight = G.O.viewerGalleryTHeight + 10;
10095 }
10096 if( G.O.viewerGallery == 'bottom' ) {
10097 cBottom = galleryHeight;
10098 }
10099
10100
10101 switch( G.O.viewerToolbar.position ) {
10102 case 'top':
10103 case 'topOverImage':
10104 G.VOM.$content.css({height: windowsH, width: windowsW, top: 0 });
10105 G.VOM.$toolbar.css({top: 0, bottom: ''});
10106 break;
10107 // case 'top':
10108 // windowsH -= tb_OHt;
10109 // G.VOM.$content.css({height: windowsH, width: windowsW, top: tb_OHt });
10110 // G.VOM.$toolbar.css({top: 0});
10111 // break;
10112 case 'bottom':
10113 case 'bottomOverImage':
10114 default:
10115 windowsH -= cBottom;
10116 G.VOM.$content.css({height: windowsH, width: windowsW, bottom: -cBottom, top: 0 });
10117 G.VOM.$toolbar.css({bottom: galleryHeight});
10118 break;
10119 // case 'bottom':
10120 // default:
10121 // windowsH -= tb_OHt;
10122 // G.VOM.$content.css({ width: windowsW, top: 0, bottom: tb_OHt });
10123 // G.VOM.$toolbar.css({bottom: galleryHeight});
10124 // break;
10125 }
10126
10127
10128 if( !G.VOM.viewerMediaIsChanged && G.VOM.zoom.isZooming ) {
10129 ViewerMediaSetPosAndZoom();
10130 }
10131 else {
10132 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 )) {
10133 // animate image zoom factor and position back to initial values
10134 G.VOM.zoom.isZooming= true; // activate zooming temporarily
10135 new NGTweenable().tween({
10136 from: { userFactor: G.VOM.zoom.userFactor, panPosX: G.VOM.panPosX, panPosY: G.VOM.panPosY, zoomPosX: G.VOM.zoom.posX, zoomPosY: G.VOM.zoom.posY },
10137 to: { userFactor: 1, panPosX: 0, panPosY: 0, zoomPosX: 0, zoomPosY: 0 },
10138 easing: 'easeInOutSine',
10139 delay: 0,
10140 duration: 150,
10141 step: function (state) {
10142 G.VOM.zoom.userFactor = state.userFactor;
10143 G.VOM.panPosX = state.panPosX;
10144 G.VOM.panPosY = state.panPosY;
10145 G.VOM.zoom.posX = state.zoomPosX;
10146 G.VOM.zoom.posY = state.zoomPosY;
10147 ViewerMediaSetPosAndZoom();
10148 },
10149 finish: function (state) {
10150 G.VOM.zoom.isZooming=false;
10151 }
10152 });
10153
10154 }
10155 else {
10156 G.VOM.zoom.userFactor = 1;
10157 G.VOM.zoom.isZooming = false;
10158 G.VOM.panPosX = 0;
10159 G.VOM.panPosY = 0;
10160 G.VOM.zoom.posX = 0;
10161 G.VOM.zoom.posY = 0;
10162 ViewerMediaSetPosAndZoom();
10163 }
10164 }
10165 }
10166
10167 // Retrieve the first parent element which is scrollable
10168 // source: ncubica - https://stackoverflow.com/questions/35939886/find-first-scrollable-parent
10169 // returns null if nothing found
10170 function getScrollableParent (node) {
10171 const regex = /(auto|scroll)/;
10172 const parents = (_node, ps) => {
10173 if (_node.parentNode === null) { return ps; }
10174 return parents(_node.parentNode, ps.concat([_node]));
10175 };
10176
10177 const style = (_node, prop) => getComputedStyle(_node, null).getPropertyValue(prop);
10178 const overflow = _node => style(_node, 'overflow') + style(_node, 'overflow-y') + style(_node, 'overflow-x');
10179 const scroll = _node => regex.test(overflow(_node));
10180
10181 const scrollParent = (_node) => {
10182 if (!(_node instanceof HTMLElement || _node instanceof SVGElement)) {
10183 return undefined;
10184 }
10185
10186 const ps = parents(_node.parentNode, []);
10187
10188 for (let i = 0; i < ps.length; i += 1) {
10189 if( ps[i] === document.body ) {
10190 return null;
10191 }
10192 if (scroll(ps[i])) {
10193 return ps[i];
10194 }
10195 }
10196
10197 return document.scrollingElement || document.documentElement;
10198 };
10199
10200 return scrollParent(node);
10201 };
10202
10203
10204
10205 /** @function BuildSkeleton */
10206 /** Build the gallery structure **/
10207 function BuildSkeleton() {
10208
10209
10210 // store markup if defined
10211 // var $elements = G.$E.base.children('a');
10212 var $elements = G.$E.base.children();
10213 if( $elements.length > 0 ) {
10214 G.O.$markup = $elements;
10215 }
10216
10217 if( !G.O.lightboxStandalone ) {
10218 G.$E.base.text('');
10219 G.$E.base.addClass('ngy2_container');
10220
10221 // RTL or LTR
10222 // var sRTL='';
10223 // if( G.O.RTL ) {
10224 // sRTL = 'style="text-align:right;direction:rtl;"';
10225 // }
10226
10227 // theme
10228 G.$E.base.addClass(G.O.theme)
10229 // gallery color scheme
10230 SetGalleryTheme();
10231
10232 // Hide icons (thumbnails and breadcrumb)
10233 if( G.O.thumbnailLabel.get('hideIcons') ) {
10234 G.O.icons.thumbnailAlbum = '';
10235 G.O.icons.thumbnailImage = '';
10236 }
10237
10238 // Navigation bar
10239 var styleNavigation="";
10240 if( G.O.navigationFontSize != undefined && G.O.navigationFontSize != '' ) {
10241 styleNavigation=' style="font-size:'+G.O.navigationFontSize+';"';
10242 }
10243 G.$E.conNavigationBar = jQuery('<div class="nGY2Navigationbar" '+styleNavigation+'></div>').appendTo(G.$E.base);
10244
10245 // pre-loader
10246 G.$E.conLoadingB = jQuery('<div class="nanoGalleryLBarOff"><div></div><div></div><div></div><div></div><div></div></div>').appendTo(G.$E.base);
10247
10248 // gallery
10249 G.$E.conTnParent = jQuery('<div class="nGY2Gallery"></div>').appendTo( G.$E.base );
10250 G.$E.conTn = jQuery('<div class="nGY2GallerySub"></div>').appendTo( G.$E.conTnParent );
10251
10252 // configure gallery
10253 switch( G.O.thumbnailAlignment ) {
10254 case 'left':
10255 G.$E.conTnParent.css({'text-align':'left'});
10256 // G.$E.conNavBCon.css({'margin-left':0 });
10257 break;
10258 case 'right':
10259 G.$E.conTnParent.css({'text-align':'right'});
10260 // G.$E.conNavBCon.css({ 'margin-right':0});
10261 break;
10262 }
10263
10264 // apply galleryBuildInit2 css settings to the gallery
10265 if( G.O.galleryBuildInit2 !== undefined ) {
10266 var t1=G.O.galleryBuildInit2.split('|');
10267 for( var i=0; i<t1.length; i++ ) {
10268 var o1=t1[i].split('_');
10269 if( o1.length == 2 ) {
10270 G.$E.conTn.css(o1[0], o1[1]);
10271 }
10272 }
10273 }
10274
10275 // configure gallery depending on some thumbnail hover effects
10276 var effects=G.tn.hoverEffects.std.concat(G.tn.hoverEffects.level1);
10277 for( var j=0; j<effects.length; j++) {
10278 switch( effects[j].type ) {
10279 case 'scale':
10280 case 'rotateZ':
10281 case 'rotateX':
10282 case 'rotateY':
10283 case 'translateX':
10284 case 'translateY':
10285 // handle some special cases
10286 if( effects[j].element == '.nGY2GThumbnail' ) {
10287 // allow thumbnail upscale over the gallery's aera
10288 G.$E.base.css('overflow', 'visible');
10289 G.$E.base.find('.nGY2GallerySub').css('overflow', 'visible');
10290 G.$E.conTnParent.css('overflow', 'visible');
10291 }
10292 break;
10293 }
10294 }
10295
10296 // Gallery bottom container
10297 G.$E.conTnBottom = jQuery('<div class="nGY2GalleryBottom" '+styleNavigation+'></div>').appendTo( G.$E.conTnParent );
10298
10299 // portable edition
10300 if( G.O.portable ) {
10301 // http://www.picresize.com/
10302 // http://base64encode.net/base64-image-encoder
10303 // 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';
10304 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';
10305 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;";
10306 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);
10307
10308 G.$E.ngy2i.find('a').on({
10309 mouseenter: function () {
10310 jQuery(this).attr('style', st);
10311 },
10312 mouseleave: function () {
10313 jQuery(this).attr('style', st);
10314 }
10315 });
10316 }
10317 }
10318
10319 // Error console
10320 G.$E.conConsole = jQuery('<div class="nGY2ConsoleParent"></div>').appendTo(G.$E.base);
10321
10322 // i18n translations
10323 i18n();
10324
10325 if( !G.O.lightboxStandalone ) {
10326 // cache some thumbnails data (sizes, styles...)
10327 ThumbnailDefCaches();
10328
10329 // do special settings depending for some options
10330 // thumbnail display transition
10331 switch( G.tn.opt.Get('displayTransition') ) {
10332 case 'SCALEDOWN':
10333 case 'RANDOMSCALE':
10334 default:
10335 G.$E.base.css('overflow', 'visible');
10336 G.$E.conTnParent.css('overflow', 'visible');
10337 G.$E.conTn.css('overflow', 'visible');
10338 break;
10339 }
10340 }
10341
10342 }
10343
10344 function TriggerCustomEvent ( eventName ) {
10345 // G.$E.base.trigger('pageChanged.nanogallery2', new Event('pageChanged.nanogallery2'));
10346 var eN = eventName + '.nanogallery2';
10347 var event=null;
10348 try {
10349 event = new Event( eN );
10350 } catch(e) {
10351 event = document.createEvent('Event');
10352 event.initEvent(eN, false, false);
10353 }
10354 G.$E.base.trigger(eN, event);
10355 }
10356
10357
10358 /** @function SetGlobalEvents */
10359 function SetGlobalEvents() {
10360 // GLOBAL EVENT MANAGEMENT
10361
10362 if( !G.O.lightboxStandalone ) {
10363 G.$E.conTnParent.on({
10364 mouseenter: GalleryMouseEnter,
10365 mouseleave: GalleryMouseLeave
10366 }, ".nGY2GThumbnail"); //pass the element as an argument to .on
10367
10368 // G.GOM.hammertime = new NGHammer(G.$E.conTn[0], { touchAction: 'none' });
10369 G.GOM.hammertime = new NGHammer( G.$E.conTn[0] );
10370 // G.GOM.hammertime.domEvents = true;
10371
10372
10373 // PAN on gallery (pagination)
10374 G.GOM.hammertime.on('pan', function(ev) {
10375 if( !G.VOM.viewerDisplayed ) {
10376 if( G.O.paginationSwipe && G.layout.support.rows && G.galleryDisplayMode.Get() == 'PAGINATION' ) {
10377 if( Math.abs(ev.deltaY) > G.GOM.panThreshold ) {
10378 G.GOM.panYOnly = true;
10379 }
10380 if( !G.GOM.panYOnly ) {
10381 G.$E.conTn.css( G.CSStransformName , 'translate('+(ev.deltaX)+'px,0px)');
10382 }
10383 }
10384 }
10385 });
10386 G.GOM.hammertime.on('panend', function(ev) {
10387 if( !G.VOM.viewerDisplayed ) {
10388 if( G.O.paginationSwipe && G.layout.support.rows && G.galleryDisplayMode.Get() == 'PAGINATION' ) {
10389 if( !G.GOM.panYOnly ) {
10390 if( ev.deltaX > 50 ) {
10391 paginationPreviousPage();
10392 return;
10393 }
10394 if( ev.deltaX < -50 ) {
10395 paginationNextPage();
10396 return;
10397 }
10398 }
10399 G.GOM.panYOnly = false;
10400 G.$E.conTn.css( G.CSStransformName , 'translate(0px,0px)');
10401 // pX=0;
10402 }
10403 }
10404 });
10405 // tap on gallery
10406 G.GOM.hammertime.on('tap', function(ev) {
10407 if( !G.VOM.viewerDisplayed ) {
10408 ev.srcEvent.stopPropagation();
10409 ev.srcEvent.preventDefault(); // cancel mouseenter event
10410
10411 if( ev.pointerType == 'mouse') {
10412 if( GalleryClicked(ev.srcEvent) == 'exit' ) { return; }
10413 }
10414 else {
10415 var r = GalleryEventRetrieveElementl(ev.srcEvent, false);
10416 if( r.GOMidx == -1 ) { return; }
10417 if( r.action != 'NONE' && r.action != 'OPEN' ) {
10418 // toolbar touched --> execute action
10419 GalleryClicked(ev.srcEvent);
10420 return;
10421 }
10422
10423 if( G.GOM.slider.hostIdx == r.GOMidx ) {
10424 // touch on thumbnail slider -> open immediately
10425 ThumbnailHoverOutAll();
10426 ThumbnailOpen(G.GOM.items[G.GOM.slider.currentIdx].thumbnailIdx, true);
10427 return;
10428 }
10429
10430 if( (G.GOM.curNavLevel == 'l1' && G.O.touchAnimationL1 == false) || (G.GOM.curNavLevel == 'lN' && G.O.touchAnimation == false) ) {
10431 // open on single touch (no hover animation)
10432 ThumbnailOpen(G.GOM.items[r.GOMidx].thumbnailIdx, true);
10433 return;
10434 }
10435
10436 if( G.O.touchAutoOpenDelay > 0 ) {
10437 // open on single touch after end of hover animation (=defined delay)
10438 ThumbnailHoverOutAll();
10439 ThumbnailHover( r.GOMidx );
10440 window.clearInterval( G.touchAutoOpenDelayTimerID );
10441 G.touchAutoOpenDelayTimerID = window.setInterval(function(){
10442 window.clearInterval( G.touchAutoOpenDelayTimerID );
10443 ThumbnailOpen( G.GOM.items[r.GOMidx].thumbnailIdx, true );
10444 }, G.O.touchAutoOpenDelay );
10445 }
10446 else {
10447 // two touch scenario
10448 if( !G.I[G.GOM.items[r.GOMidx].thumbnailIdx].hovered ) {
10449 ThumbnailHoverOutAll();
10450 ThumbnailHover(r.GOMidx);
10451 }
10452 else {
10453 // second touch
10454 ThumbnailOpen(G.GOM.items[r.GOMidx].thumbnailIdx, true);
10455 }
10456 }
10457 }
10458 }
10459 });
10460
10461 // browser location hash management
10462 if( G.O.locationHash ) {
10463 // jQuery(window).bind( 'hashchange', function() {
10464 // ProcessLocationHash();
10465 // });
10466 jQuery(window).on('hashchange.nanogallery2.' + G.baseEltID, function() {ProcessLocationHash();} );
10467 }
10468 }
10469
10470 // Page resize / orientation change
10471 jQuery(window).on('resize.nanogallery2.' + G.baseEltID + ' orientationChange.nanogallery2.' + G.baseEltID, debounce( ResizeWindowEvent, G.O.eventsDebounceDelay, false) );
10472
10473 // Event page scrolled
10474 jQuery(window).on('scroll.nanogallery2.' + G.baseEltID, debounce( OnScrollEvent, G.O.eventsDebounceDelay, false) );
10475
10476 if( !G.O.lightboxStandalone ) {
10477 // Scroll event on first scrollable parent element
10478 G.$E.scrollableParent = getScrollableParent( G.$E.base[0] );
10479 var sp = getScrollableParent( G.$E.base[0] );
10480 if( sp !== null ) {
10481 G.$E.scrollableParent = jQuery( sp );
10482 G.$E.scrollableParent.on('scroll.nanogallery2.' + G.baseEltID, debounce( OnScrollEvent, G.O.eventsDebounceDelay, false) );
10483 }
10484 }
10485
10486 // lightbox: hide tools/gallery after defined delay
10487 G.VOM.toolsHide = debounce( ViewerToolsHide, G.O.viewerHideToolsDelay, false );
10488
10489 // Keyboard management
10490 jQuery(document).keyup(function(e) {
10491 if( G.popup.isDisplayed ) {
10492 switch( e.keyCode) {
10493 case 27: // Esc key
10494 G.popup.close();
10495 break;
10496 }
10497 }
10498 else {
10499 if( G.VOM.viewerDisplayed ) {
10500 ViewerToolsUnHide();
10501 switch( e.keyCode) {
10502 case 27: // Escape key
10503 case 40: // DOWN
10504 case 38: // UP
10505 LightboxClose();
10506 break;
10507 case 32: // SPACE
10508 case 13: // ENTER
10509 SlideshowToggle();
10510 break;
10511 case 39: // RIGHT
10512 case 33: // PAGE UP
10513 DisplayNextMedia();
10514 break;
10515 case 37: // LEFT
10516 case 34: // PAGE DOWN
10517 DisplayPreviousMedia();
10518 break;
10519 case 35: // END
10520 case 36: // BEGIN
10521 }
10522 }
10523 }
10524 });
10525
10526 // mouse wheel to zoom in/out the image displayed in the internal lightbox
10527 jQuery(window).bind('mousewheel wheel', function(e){
10528
10529 if( G.VOM.viewerDisplayed && G.VOM.content.current.NGY2Item().mediaKind == 'img' ) {
10530
10531 var deltaY = 0;
10532 e.preventDefault();
10533
10534 if( ViewerZoomStart() ) {
10535 if (e.originalEvent.deltaY) { // FireFox 17+ (IE9+, Chrome 31+?)
10536 deltaY = e.originalEvent.deltaY;
10537 } else if (e.originalEvent.wheelDelta) {
10538 deltaY = -e.originalEvent.wheelDelta;
10539 }
10540 ViewerZoomIn( deltaY <= 0 ? true : false );
10541 }
10542 }
10543 });
10544
10545 // mouse move -> unhide lightbox toolbars
10546 jQuery(window).bind('mousemove', function(e){
10547 if( G.VOM.viewerDisplayed ) {
10548 if( G.VOM.toolbarsDisplayed == false ) {
10549 G.VOM.singletapTime = new Date().getTime(); // to avoid conflict with SINGLETAP event
10550 debounce( ViewerToolsUnHide, 100, false )();
10551 }
10552 }
10553 });
10554
10555 // fullscreen mode on/off --> internal lightbox
10556 if( ngscreenfull.enabled ) {
10557 // ngscreenfull.onchange(() => {
10558 ngscreenfull.onchange( function() {
10559 if( G.VOM.viewerDisplayed ) {
10560 if( ngscreenfull.isFullscreen ) {
10561 G.VOM.viewerIsFullscreen=true;
10562 G.VOM.$viewer.find('.fullscreenButton').html(G.O.icons.viewerFullscreenOff);
10563 }
10564 else {
10565 G.VOM.viewerIsFullscreen=false;
10566 G.VOM.$viewer.find('.fullscreenButton').html(G.O.icons.viewerFullscreenOn);
10567 }
10568 }
10569 });
10570 }
10571
10572 }
10573
10574
10575 //----- Manage browser location hash (deep linking and browser back/forward)
10576 function ProcessLocationHash() {
10577
10578 // standard use case -> location hash processing
10579 if( !G.O.locationHash ) { return false; }
10580
10581 var curGal = '#nanogallery/' + G.baseEltID + '/',
10582 newLocationHash = location.hash;
10583 if( G.O.debugMode ) {
10584 console.log('------------------------ PROCESS LOCATION HASH');
10585 console.log('newLocationHash1: ' +newLocationHash);
10586 console.log('G.locationHashLastUsed: ' +G.locationHashLastUsed);
10587 }
10588
10589 if( newLocationHash == '' ) {
10590 // if( G.GOM.lastDisplayedIdx != -1 ) {
10591 if( G.locationHashLastUsed !== '' ) {
10592 // back button and no hash --> display first album
10593 if( G.O.debugMode ) { console.log('display root album' ); }
10594 G.locationHashLastUsed = '';
10595 if( G.O.debugMode ) { console.log('new3 G.locationHashLastUsed: ' + G.locationHashLastUsed); }
10596 DisplayAlbum('', '0');
10597 return true;
10598 }
10599 }
10600
10601 if( newLocationHash == G.locationHashLastUsed ) { return; }
10602
10603 if( newLocationHash.indexOf(curGal) == 0 ) {
10604 // item IDs detected
10605 var IDs=parseIDs( newLocationHash.substring(curGal.length) );
10606 if( IDs.imageID != '0' ) {
10607 if( G.O.debugMode ) { console.log('display image: ' + IDs.albumID +'-'+ IDs.imageID ); }
10608 DisplayPhoto( IDs.imageID, IDs.albumID );
10609 return true;
10610 }
10611 else {
10612 if( G.O.debugMode ) { console.log('display album: ' + IDs.albumID ); }
10613 DisplayAlbum( '-1', IDs.albumID );
10614 return true;
10615 }
10616 }
10617
10618 return false;
10619 }
10620
10621 //---- Set a new browser location hash
10622 function SetLocationHash(albumID, imageID ) {
10623 if( !G.O.locationHash || G.O.lightboxStandalone ) { return false; }
10624
10625 if( G.O.debugMode ) {
10626 console.log('------------------------ SET LOCATION HASH');
10627 }
10628
10629 if( imageID == '' && (albumID == '-1' || albumID == '0' || G.O.album == albumID ) ) {
10630 // root album level --> do not set top.location.hash if not already set
10631 if( location.hash != '' ) {
10632 // try to clear the hash if set
10633 if ("pushState" in history) {
10634 history.pushState("", document.title, window.location.pathname + window.location.search);
10635 }
10636 else {
10637 location.hash='';
10638 }
10639 }
10640 G.locationHashLastUsed='';
10641 if( G.O.debugMode ) { console.log('new2 G.locationHashLastUsed: '+G.locationHashLastUsed); }
10642 return;
10643 }
10644
10645 var newLocationHash='#'+'nanogallery/'+G.baseEltID+'/'+ albumID;
10646 if( imageID != '' ) {
10647 newLocationHash+='/'+imageID;
10648 }
10649
10650 var lH=location.hash;
10651 if( G.O.debugMode ) {
10652 console.log('newLocationHash2: '+newLocationHash);
10653 console.log('location.hash: '+lH);
10654 }
10655
10656 G.locationHashLastUsed=newLocationHash;
10657 if( G.O.debugMode ) { console.log('new G.locationHashLastUsed: '+G.locationHashLastUsed); }
10658
10659 if( lH == '' || lH != newLocationHash ) {
10660 // G.locationHashLastUsed='#'+newLocationHash;
10661 try {
10662 top.location.hash=newLocationHash;
10663 }
10664 catch(e) {
10665 // location hash is not supported by current browser --> disable the option
10666 G.O.locationHash=false;
10667 }
10668 }
10669 }
10670
10671
10672 // WINDOW RESIZE EVENT
10673 function ResizeWindowEvent() {
10674 CacheViewport();
10675
10676 var l = G.GOM.curNavLevel;
10677 var w = G.GOM.curWidth;
10678
10679 if( G.VOM.viewerDisplayed ) {
10680 // lightbox
10681 ResizeLightbox();
10682 G.VOM.gallery.Resize();
10683 }
10684 else {
10685 // gallery
10686 if( G.galleryResizeEventEnabled ) {
10687 var nw = RetrieveCurWidth();
10688
10689 if( G.GOM.albumIdx != -1 ) {
10690
10691 // check if the gallery needs to be rendered again because the width changed
10692
10693 var s = G.tn.settings;
10694 if( G.layout.engine == "MOSAIC") {
10695 // Mosaic layout
10696 if( JSON.stringify(s.mosaic[l][w]) !== JSON.stringify(s.mosaic[l][nw]) ) {
10697 // mosaic definition changed
10698 G.GOM.curWidth = nw;
10699 G.GOM.pagination.currentPage = 0;
10700 GalleryRender( G.GOM.albumIdx );
10701 return;
10702 }
10703 }
10704 else {
10705 // other layouts
10706 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] ) {
10707 // thumbnail size / gutter size changed --> render the gallery with the new values
10708 G.GOM.curWidth = nw;
10709 //G.layout.SetEngine();
10710 G.GOM.pagination.currentPage = 0;
10711 GalleryRender( G.GOM.albumIdx );
10712 return;
10713 }
10714 }
10715 // return;
10716 }
10717 // else {
10718 GalleryResize();
10719 // }
10720 }
10721 }
10722 }
10723
10724
10725
10726 // SCROLL EVENT -> on WINDOW or SCROLLABLE PARENT CONTAINER
10727 function OnScrollEvent() {
10728 if( !G.VOM.viewerDisplayed ) {
10729 GalleryResizeOnScrollEvent();
10730 }
10731 }
10732
10733 // 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
10734 function GalleryResizeOnScrollEvent() {
10735 if( G.galleryResizeEventEnabled == false) {
10736 window.setTimeout(GalleryResizeOnScrollEvent, 10); // check again in 10ms
10737 } else {
10738 GalleryResize();
10739 }
10740 }
10741
10742
10743
10744 // I18N : define text translations
10745 function i18n() {
10746
10747 // browser language
10748 G.i18nLang = (navigator.language || navigator.userLanguage).toUpperCase();
10749 if( G.i18nLang === 'UNDEFINED') { G.i18nLang=''; }
10750
10751 var llang=-('_'+G.i18nLang).length;
10752
10753 if( toType(G.O.i18n) == 'object' ){
10754
10755 for( var key in G.O.i18n ) {
10756 //var value = G.O.i18n[key];
10757 var s=key.substr(llang);
10758 if( s == ('_'+G.i18nLang) ) {
10759 G.i18nTranslations[key.substr(0,key.length-s.length)]=G.O.i18n[key];
10760 }
10761 else {
10762 G.i18nTranslations[key]=G.O.i18n[key];
10763 }
10764 }
10765 }
10766 }
10767
10768 function GetI18nItem( item, property ) {
10769 var s='';
10770 if( G.i18nLang != '' ) {
10771 if( item[property+'_'+G.i18nLang] !== undefined && item[property+'_'+G.i18nLang].length>0 ) {
10772 s=item[property+'_'+G.i18nLang];
10773 return s;
10774 }
10775 }
10776 s=item[property];
10777 return s;
10778 }
10779
10780
10781 function RetrieveCurWidth() {
10782 var vpW = G.GOM.cache.viewport.w;
10783
10784 if( G.O.breakpointSizeSM > 0 && vpW < G.O.breakpointSizeSM) { return 'xs'; }
10785 if( G.O.breakpointSizeME > 0 && vpW < G.O.breakpointSizeME) { return 'sm'; }
10786 if( G.O.breakpointSizeLA > 0 && vpW < G.O.breakpointSizeLA) { return 'me'; }
10787 if( G.O.breakpointSizeXL > 0 && vpW < G.O.breakpointSizeXL) { return 'la'; }
10788
10789 return 'xl';
10790 }
10791
10792
10793 /** @function browserNotification */
10794 function browserNotification() {
10795 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>';
10796 m += '&nbsp;&nbsp;&nbsp; <a href="http://www.google.com/chrome/?hl=en-US)">Chrome</a><br>';
10797 m += '&nbsp;&nbsp;&nbsp; <a href="http://www.mozilla.com/firefox/)">Firefox</a><br>';
10798 m += '&nbsp;&nbsp;&nbsp; <a href="http://www.microsoft.com/windows/internet-explorer/default.aspx">Internet Explorer</a><br>';
10799 m += '&nbsp;&nbsp;&nbsp; <a href="http://www.apple.com/safari/download/">Safari</a>';
10800 NanoAlert(G, m, false);
10801 }
10802
10803 // 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
10804 function FirstSupportedPropertyName(prefixedPropertyNames) {
10805 var tempDiv = document.createElement("div");
10806 for (var i = 0; i < prefixedPropertyNames.length; ++i) {
10807 if (typeof tempDiv.style[prefixedPropertyNames[i]] != 'undefined')
10808 return prefixedPropertyNames[i];
10809 }
10810 return null;
10811 }
10812
10813
10814
10815
10816 }
10817
10818
10819
10820//##########################################################################################################################
10821//## imagesLoaded ##########################################################################################################
10822//##########################################################################################################################
10823
10824// external module EMBEDED in nanogallery
10825// NGY BUILD:
10826// replace "imagesLoaded" with "ngimagesLoaded"
10827// replace "ImagesLoaded" with "ngImagesLoaded"
10828// replace "EvEmitter" with "ngEvEmitter"
10829// replace "var $ = window.jQuery" with "var $ = jQuery;"
10830// 2x (global.ngEvEmitter and window.ngimagesLoaded = f...)ignore package manager and set browser global
10831
10832
10833
10834/*!
10835 * imagesLoaded PACKAGED v4.1.1
10836 * JavaScript is all like "You images are done yet or what?"
10837 * MIT License
10838 */
10839
10840/**
10841 * EvEmitter v1.0.3
10842 * Lil' event emitter
10843 * MIT License
10844 */
10845
10846
10847/* jshint unused: true, undef: true, strict: true */
10848
10849( function( global, factory ) {
10850 // universal module definition
10851 /* jshint strict: false */ /* globals define, module, window */
10852// if ( typeof define == 'function' && define.amd ) {
10853 // AMD - RequireJS
10854// define( 'ev-emitter/ev-emitter',factory );
10855// } else if ( typeof module == 'object' && module.exports ) {
10856 // CommonJS - Browserify, Webpack
10857// module.exports = factory();
10858// } else {
10859 // Browser globals
10860 global.ngEvEmitter = factory();
10861// }
10862
10863}( typeof window != 'undefined' ? window : this, function() {
10864
10865
10866
10867function ngEvEmitter() {}
10868
10869var proto = ngEvEmitter.prototype;
10870
10871proto.on = function( eventName, listener ) {
10872 if ( !eventName || !listener ) {
10873 return;
10874 }
10875 // set events hash
10876 var events = this._events = this._events || {};
10877 // set listeners array
10878 var listeners = events[ eventName ] = events[ eventName ] || [];
10879 // only add once
10880 if ( listeners.indexOf( listener ) == -1 ) {
10881 listeners.push( listener );
10882 }
10883
10884 return this;
10885};
10886
10887proto.once = function( eventName, listener ) {
10888 if ( !eventName || !listener ) {
10889 return;
10890 }
10891 // add event
10892 this.on( eventName, listener );
10893 // set once flag
10894 // set onceEvents hash
10895 var onceEvents = this._onceEvents = this._onceEvents || {};
10896 // set onceListeners object
10897 var onceListeners = onceEvents[ eventName ] = onceEvents[ eventName ] || {};
10898 // set flag
10899 onceListeners[ listener ] = true;
10900
10901 return this;
10902};
10903
10904proto.off = function( eventName, listener ) {
10905 var listeners = this._events && this._events[ eventName ];
10906 if ( !listeners || !listeners.length ) {
10907 return;
10908 }
10909 var index = listeners.indexOf( listener );
10910 if ( index != -1 ) {
10911 listeners.splice( index, 1 );
10912 }
10913
10914 return this;
10915};
10916
10917proto.emitEvent = function( eventName, args ) {
10918 var listeners = this._events && this._events[ eventName ];
10919 if ( !listeners || !listeners.length ) {
10920 return;
10921 }
10922 var i = 0;
10923 var listener = listeners[i];
10924 args = args || [];
10925 // once stuff
10926 var onceListeners = this._onceEvents && this._onceEvents[ eventName ];
10927
10928 while ( listener ) {
10929 var isOnce = onceListeners && onceListeners[ listener ];
10930 if ( isOnce ) {
10931 // remove listener
10932 // remove before trigger to prevent recursion
10933 this.off( eventName, listener );
10934 // unset once flag
10935 delete onceListeners[ listener ];
10936 }
10937 // trigger listener
10938 listener.apply( this, args );
10939 // get next listener
10940 i += isOnce ? 0 : 1;
10941 listener = listeners[i];
10942 }
10943
10944 return this;
10945};
10946
10947return ngEvEmitter;
10948
10949}));
10950
10951/*!
10952 * ngimagesLoaded v4.1.1
10953 * JavaScript is all like "You images are done yet or what?"
10954 * MIT License
10955 */
10956
10957( function( window, factory ) { 'use strict';
10958 // universal module definition
10959
10960 /*global define: false, module: false, require: false */
10961
10962// if ( typeof define == 'function' && define.amd ) {
10963 // AMD
10964// define( [
10965// 'ev-emitter/ev-emitter'
10966// ], function( ngEvEmitter ) {
10967// return factory( window, ngEvEmitter );
10968// });
10969// } else if ( typeof module == 'object' && module.exports ) {
10970 // CommonJS
10971// module.exports = factory(
10972// window,
10973// require('ev-emitter')
10974// );
10975// } else {
10976 // browser global
10977 window.ngimagesLoaded = factory(
10978 window,
10979 window.ngEvEmitter
10980 );
10981 //}
10982
10983})( window,
10984
10985// -------------------------- factory -------------------------- //
10986
10987function factory( window, ngEvEmitter ) {
10988
10989
10990
10991// var $ = window.jQuery;
10992var $ = jQuery;
10993var console = window.console;
10994
10995// -------------------------- helpers -------------------------- //
10996
10997// extend objects
10998function extend( a, b ) {
10999 for ( var prop in b ) {
11000 a[ prop ] = b[ prop ];
11001 }
11002 return a;
11003}
11004
11005// turn element or nodeList into an array
11006function makeArray( obj ) {
11007 var ary = [];
11008 if ( Array.isArray( obj ) ) {
11009 // use object if already an array
11010 ary = obj;
11011 } else if ( typeof obj.length == 'number' ) {
11012 // convert nodeList to array
11013 for ( var i=0; i < obj.length; i++ ) {
11014 ary.push( obj[i] );
11015 }
11016 } else {
11017 // array of single index
11018 ary.push( obj );
11019 }
11020 return ary;
11021}
11022
11023// -------------------------- ngimagesLoaded -------------------------- //
11024
11025/**
11026 * @param {Array, Element, NodeList, String} elem
11027 * @param {Object or Function} options - if function, use as callback
11028 * @param {Function} onAlways - callback function
11029 */
11030function ngImagesLoaded( elem, options, onAlways ) {
11031 // coerce ngImagesLoaded() without new, to be new ngImagesLoaded()
11032 if ( !( this instanceof ngImagesLoaded ) ) {
11033 return new ngImagesLoaded( elem, options, onAlways );
11034 }
11035 // use elem as selector string
11036 if ( typeof elem == 'string' ) {
11037 elem = document.querySelectorAll( elem );
11038 }
11039
11040 this.elements = makeArray( elem );
11041 this.options = extend( {}, this.options );
11042
11043 if ( typeof options == 'function' ) {
11044 onAlways = options;
11045 } else {
11046 extend( this.options, options );
11047 }
11048
11049 if ( onAlways ) {
11050 this.on( 'always', onAlways );
11051 }
11052
11053 this.getImages();
11054
11055 if ( $ ) {
11056 // add jQuery Deferred object
11057 this.jqDeferred = new $.Deferred();
11058 }
11059
11060 // HACK check async to allow time to bind listeners
11061 setTimeout( function() {
11062 this.check();
11063 }.bind( this ));
11064}
11065
11066ngImagesLoaded.prototype = Object.create( ngEvEmitter.prototype );
11067
11068ngImagesLoaded.prototype.options = {};
11069
11070ngImagesLoaded.prototype.getImages = function() {
11071 this.images = [];
11072
11073 // filter & find items if we have an item selector
11074 this.elements.forEach( this.addElementImages, this );
11075};
11076
11077/**
11078 * @param {Node} element
11079 */
11080ngImagesLoaded.prototype.addElementImages = function( elem ) {
11081 // filter siblings
11082 if ( elem.nodeName == 'IMG' ) {
11083 this.addImage( elem );
11084 }
11085 // get background image on element
11086 if ( this.options.background === true ) {
11087 this.addElementBackgroundImages( elem );
11088 }
11089
11090 // find children
11091 // no non-element nodes, #143
11092 var nodeType = elem.nodeType;
11093 if ( !nodeType || !elementNodeTypes[ nodeType ] ) {
11094 return;
11095 }
11096 var childImgs = elem.querySelectorAll('img');
11097 // concat childElems to filterFound array
11098 for ( var i=0; i < childImgs.length; i++ ) {
11099 var img = childImgs[i];
11100 this.addImage( img );
11101 }
11102
11103 // get child background images
11104 if ( typeof this.options.background == 'string' ) {
11105 var children = elem.querySelectorAll( this.options.background );
11106 for ( i=0; i < children.length; i++ ) {
11107 var child = children[i];
11108 this.addElementBackgroundImages( child );
11109 }
11110 }
11111};
11112
11113var elementNodeTypes = {
11114 1: true,
11115 9: true,
11116 11: true
11117};
11118
11119ngImagesLoaded.prototype.addElementBackgroundImages = function( elem ) {
11120 var style = getComputedStyle( elem );
11121 if ( !style ) {
11122 // Firefox returns null if in a hidden iframe https://bugzil.la/548397
11123 return;
11124 }
11125 // get url inside url("...")
11126 var reURL = /url\((['"])?(.*?)\1\)/gi;
11127 var matches = reURL.exec( style.backgroundImage );
11128 while ( matches !== null ) {
11129 var url = matches && matches[2];
11130 if ( url ) {
11131 this.addBackground( url, elem );
11132 }
11133 matches = reURL.exec( style.backgroundImage );
11134 }
11135};
11136
11137/**
11138 * @param {Image} img
11139 */
11140ngImagesLoaded.prototype.addImage = function( img ) {
11141 var loadingImage = new LoadingImage( img );
11142 this.images.push( loadingImage );
11143};
11144
11145ngImagesLoaded.prototype.addBackground = function( url, elem ) {
11146 var background = new Background( url, elem );
11147 this.images.push( background );
11148};
11149
11150ngImagesLoaded.prototype.check = function() {
11151 var _this = this;
11152 this.progressedCount = 0;
11153 this.hasAnyBroken = false;
11154 // complete if no images
11155 if ( !this.images.length ) {
11156 this.complete();
11157 return;
11158 }
11159
11160 function onProgress( image, elem, message ) {
11161 // HACK - Chrome triggers event before object properties have changed. #83
11162 setTimeout( function() {
11163 _this.progress( image, elem, message );
11164 });
11165 }
11166
11167 this.images.forEach( function( loadingImage ) {
11168 loadingImage.once( 'progress', onProgress );
11169 loadingImage.check();
11170 });
11171};
11172
11173ngImagesLoaded.prototype.progress = function( image, elem, message ) {
11174 this.progressedCount++;
11175 this.hasAnyBroken = this.hasAnyBroken || !image.isLoaded;
11176 // progress event
11177 this.emitEvent( 'progress', [ this, image, elem ] );
11178 if ( this.jqDeferred && this.jqDeferred.notify ) {
11179 this.jqDeferred.notify( this, image );
11180 }
11181 // check if completed
11182 if ( this.progressedCount == this.images.length ) {
11183 this.complete();
11184 }
11185
11186 if ( this.options.debug && console ) {
11187 console.log( 'progress: ' + message, image, elem );
11188 }
11189};
11190
11191ngImagesLoaded.prototype.complete = function() {
11192 var eventName = this.hasAnyBroken ? 'fail' : 'done';
11193 this.isComplete = true;
11194 this.emitEvent( eventName, [ this ] );
11195 this.emitEvent( 'always', [ this ] );
11196 if ( this.jqDeferred ) {
11197 var jqMethod = this.hasAnyBroken ? 'reject' : 'resolve';
11198 this.jqDeferred[ jqMethod ]( this );
11199 }
11200};
11201
11202// -------------------------- -------------------------- //
11203
11204function LoadingImage( img ) {
11205 this.img = img;
11206}
11207
11208LoadingImage.prototype = Object.create( ngEvEmitter.prototype );
11209
11210LoadingImage.prototype.check = function() {
11211 // If complete is true and browser supports natural sizes,
11212 // try to check for image status manually.
11213 var isComplete = this.getIsImageComplete();
11214 if ( isComplete ) {
11215 // report based on naturalWidth
11216 this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' );
11217 return;
11218 }
11219
11220 // If none of the checks above matched, simulate loading on detached element.
11221 this.proxyImage = new Image();
11222 this.proxyImage.addEventListener( 'load', this );
11223 this.proxyImage.addEventListener( 'error', this );
11224 // bind to image as well for Firefox. #191
11225 this.img.addEventListener( 'load', this );
11226 this.img.addEventListener( 'error', this );
11227 this.proxyImage.src = this.img.src;
11228};
11229
11230LoadingImage.prototype.getIsImageComplete = function() {
11231 return this.img.complete && this.img.naturalWidth !== undefined;
11232};
11233
11234LoadingImage.prototype.confirm = function( isLoaded, message ) {
11235 this.isLoaded = isLoaded;
11236 this.emitEvent( 'progress', [ this, this.img, message ] );
11237};
11238
11239// ----- events ----- //
11240
11241// trigger specified handler for event type
11242LoadingImage.prototype.handleEvent = function( event ) {
11243 var method = 'on' + event.type;
11244 if ( this[ method ] ) {
11245 this[ method ]( event );
11246 }
11247};
11248
11249LoadingImage.prototype.onload = function() {
11250 this.confirm( true, 'onload' );
11251 this.unbindEvents();
11252};
11253
11254LoadingImage.prototype.onerror = function() {
11255 this.confirm( false, 'onerror' );
11256 this.unbindEvents();
11257};
11258
11259LoadingImage.prototype.unbindEvents = function() {
11260 this.proxyImage.removeEventListener( 'load', this );
11261 this.proxyImage.removeEventListener( 'error', this );
11262 this.img.removeEventListener( 'load', this );
11263 this.img.removeEventListener( 'error', this );
11264};
11265
11266// -------------------------- Background -------------------------- //
11267
11268function Background( url, element ) {
11269 this.url = url;
11270 this.element = element;
11271 this.img = new Image();
11272}
11273
11274// inherit LoadingImage prototype
11275Background.prototype = Object.create( LoadingImage.prototype );
11276
11277Background.prototype.check = function() {
11278 this.img.addEventListener( 'load', this );
11279 this.img.addEventListener( 'error', this );
11280 this.img.src = this.url;
11281 // check if image is already complete
11282 var isComplete = this.getIsImageComplete();
11283 if ( isComplete ) {
11284 this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' );
11285 this.unbindEvents();
11286 }
11287};
11288
11289Background.prototype.unbindEvents = function() {
11290 this.img.removeEventListener( 'load', this );
11291 this.img.removeEventListener( 'error', this );
11292};
11293
11294Background.prototype.confirm = function( isLoaded, message ) {
11295 this.isLoaded = isLoaded;
11296 this.emitEvent( 'progress', [ this, this.element, message ] );
11297};
11298
11299// -------------------------- jQuery -------------------------- //
11300
11301ngImagesLoaded.makeJQueryPlugin = function( jQuery ) {
11302 jQuery = jQuery || window.jQuery;
11303 if ( !jQuery ) {
11304 return;
11305 }
11306 // set local variable
11307 $ = jQuery;
11308 // $().ngimagesLoaded()
11309 $.fn.ngimagesLoaded = function( options, callback ) {
11310 var instance = new ngImagesLoaded( this, options, callback );
11311 return instance.jqDeferred.promise( $(this) );
11312 };
11313};
11314// try making plugin
11315ngImagesLoaded.makeJQueryPlugin();
11316
11317// -------------------------- -------------------------- //
11318
11319return ngImagesLoaded;
11320
11321});
11322
11323
11324
11325//##########################################################################################################################
11326//## screenfull.js #########################################################################################################
11327//##########################################################################################################################
11328
11329// screenfull.js
11330// v4.0.1
11331// by sindresorhus - https://github.com/sindresorhus
11332// from: https://github.com/sindresorhus/screenfull.js
11333
11334// external module embeded in nanogallery
11335// NGY BUILD:
11336// replace "screenfull" with "ngscreenfull"
11337//
11338
11339(function () {
11340 'use strict';
11341
11342 var document = typeof window !== 'undefined' && typeof window.document !== 'undefined' ? window.document : {};
11343 var isCommonjs = typeof module !== 'undefined' && module.exports;
11344 var keyboardAllowed = typeof Element !== 'undefined' && 'ALLOW_KEYBOARD_INPUT' in Element;
11345
11346 var fn = (function () {
11347 var val;
11348
11349 var fnMap = [
11350 [
11351 'requestFullscreen',
11352 'exitFullscreen',
11353 'fullscreenElement',
11354 'fullscreenEnabled',
11355 'fullscreenchange',
11356 'fullscreenerror'
11357 ],
11358 // New WebKit
11359 [
11360 'webkitRequestFullscreen',
11361 'webkitExitFullscreen',
11362 'webkitFullscreenElement',
11363 'webkitFullscreenEnabled',
11364 'webkitfullscreenchange',
11365 'webkitfullscreenerror'
11366
11367 ],
11368 // Old WebKit (Safari 5.1)
11369 [
11370 'webkitRequestFullScreen',
11371 'webkitCancelFullScreen',
11372 'webkitCurrentFullScreenElement',
11373 'webkitCancelFullScreen',
11374 'webkitfullscreenchange',
11375 'webkitfullscreenerror'
11376
11377 ],
11378 [
11379 'mozRequestFullScreen',
11380 'mozCancelFullScreen',
11381 'mozFullScreenElement',
11382 'mozFullScreenEnabled',
11383 'mozfullscreenchange',
11384 'mozfullscreenerror'
11385 ],
11386 [
11387 'msRequestFullscreen',
11388 'msExitFullscreen',
11389 'msFullscreenElement',
11390 'msFullscreenEnabled',
11391 'MSFullscreenChange',
11392 'MSFullscreenError'
11393 ]
11394 ];
11395
11396 var i = 0;
11397 var l = fnMap.length;
11398 var ret = {};
11399
11400 for (; i < l; i++) {
11401 val = fnMap[i];
11402 if (val && val[1] in document) {
11403 for (i = 0; i < val.length; i++) {
11404 ret[fnMap[0][i]] = val[i];
11405 }
11406 return ret;
11407 }
11408 }
11409
11410 return false;
11411 })();
11412
11413 var eventNameMap = {
11414 change: fn.fullscreenchange,
11415 error: fn.fullscreenerror
11416 };
11417
11418 var ngscreenfull = {
11419 request: function (elem) {
11420 return new Promise(function (resolve) {
11421 var request = fn.requestFullscreen;
11422
11423 var onFullScreenEntered = function () {
11424 this.off('change', onFullScreenEntered);
11425 resolve();
11426 }.bind(this);
11427
11428 elem = elem || document.documentElement;
11429
11430 // Work around Safari 5.1 bug: reports support for
11431 // keyboard in fullscreen even though it doesn't.
11432 // Browser sniffing, since the alternative with
11433 // setTimeout is even worse.
11434 if (/ Version\/5\.1(?:\.\d+)? Safari\//.test(navigator.userAgent)) {
11435 elem[request]();
11436 } else {
11437 elem[request](keyboardAllowed ? Element.ALLOW_KEYBOARD_INPUT : {});
11438 }
11439
11440 this.on('change', onFullScreenEntered);
11441 }.bind(this));
11442 },
11443 exit: function () {
11444 return new Promise(function (resolve) {
11445 if (!this.isFullscreen) {
11446 resolve();
11447 return;
11448 }
11449
11450 var onFullScreenExit = function () {
11451 this.off('change', onFullScreenExit);
11452 resolve();
11453 }.bind(this);
11454
11455 document[fn.exitFullscreen]();
11456
11457 this.on('change', onFullScreenExit);
11458 }.bind(this));
11459 },
11460 toggle: function (elem) {
11461 return this.isFullscreen ? this.exit() : this.request(elem);
11462 },
11463 onchange: function (callback) {
11464 this.on('change', callback);
11465 },
11466 onerror: function (callback) {
11467 this.on('error', callback);
11468 },
11469 on: function (event, callback) {
11470 var eventName = eventNameMap[event];
11471 if (eventName) {
11472 document.addEventListener(eventName, callback, false);
11473 }
11474 },
11475 off: function (event, callback) {
11476 var eventName = eventNameMap[event];
11477 if (eventName) {
11478 document.removeEventListener(eventName, callback, false);
11479 }
11480 },
11481 raw: fn
11482 };
11483
11484 if (!fn) {
11485 if (isCommonjs) {
11486 module.exports = false;
11487 } else {
11488 window.ngscreenfull = false;
11489 }
11490
11491 return;
11492 }
11493
11494 Object.defineProperties(ngscreenfull, {
11495 isFullscreen: {
11496 get: function () {
11497 return Boolean(document[fn.fullscreenElement]);
11498 }
11499 },
11500 element: {
11501 enumerable: true,
11502 get: function () {
11503 return document[fn.fullscreenElement];
11504 }
11505 },
11506 enabled: {
11507 enumerable: true,
11508 get: function () {
11509 // Coerce to boolean in case of old WebKit
11510 return Boolean(document[fn.fullscreenEnabled]);
11511 }
11512 }
11513 });
11514
11515 if (isCommonjs) {
11516 module.exports = ngscreenfull;
11517 } else {
11518 window.ngscreenfull = ngscreenfull;
11519 }
11520})();
11521
11522
11523
11524//##########################################################################################################################
11525//## Shifty ################################################################################################################
11526//##########################################################################################################################
11527
11528 /*!
11529 * Shifty
11530 * By Jeremy Kahn - jeremyckahn@gmail.com
11531 */
11532
11533// external module EMBEDED in nanogallery
11534// NGY BUILD:
11535//
11536// replace "Tweenable" with "NGTweenable"
11537// replace "define.amd" with "define.amdDISABLED"
11538// replace "var root = this || Function('return this')();" with "const root = typeof window !== 'undefined' ? window : global"
11539
11540/* shifty - v1.5.3 - 2016-11-29 - http://jeremyckahn.github.io/shifty */
11541;(function () {
11542 const root = typeof window !== 'undefined' ? window : global
11543
11544/**
11545 * Shifty Core
11546 * By Jeremy Kahn - jeremyckahn@gmail.com
11547 */
11548
11549var NGTweenable = (function () {
11550
11551 'use strict';
11552
11553 // Aliases that get defined later in this function
11554 var formula;
11555
11556 // CONSTANTS
11557 var DEFAULT_SCHEDULE_FUNCTION;
11558 var DEFAULT_EASING = 'linear';
11559 var DEFAULT_DURATION = 500;
11560 var UPDATE_TIME = 1000 / 60;
11561
11562 var _now = Date.now
11563 ? Date.now
11564 : function () {return +new Date();};
11565
11566 var now = typeof SHIFTY_DEBUG_NOW !== 'undefined' ? SHIFTY_DEBUG_NOW : _now;
11567
11568 if (typeof window !== 'undefined') {
11569 // requestAnimationFrame() shim by Paul Irish (modified for Shifty)
11570 // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
11571 DEFAULT_SCHEDULE_FUNCTION = window.requestAnimationFrame
11572 || window.webkitRequestAnimationFrame
11573 || window.oRequestAnimationFrame
11574 || window.msRequestAnimationFrame
11575 || (window.mozCancelRequestAnimationFrame
11576 && window.mozRequestAnimationFrame)
11577 || setTimeout;
11578 } else {
11579 DEFAULT_SCHEDULE_FUNCTION = setTimeout;
11580 }
11581
11582 function noop () {
11583 // NOOP!
11584 }
11585
11586 /**
11587 * Handy shortcut for doing a for-in loop. This is not a "normal" each
11588 * function, it is optimized for Shifty. The iterator function only receives
11589 * the property name, not the value.
11590 * @param {Object} obj
11591 * @param {Function(string)} fn
11592 * @private
11593 */
11594 function each (obj, fn) {
11595 var key;
11596 for (key in obj) {
11597 if (Object.hasOwnProperty.call(obj, key)) {
11598 fn(key);
11599 }
11600 }
11601 }
11602
11603 /**
11604 * Perform a shallow copy of Object properties.
11605 * @param {Object} targetObject The object to copy into
11606 * @param {Object} srcObject The object to copy from
11607 * @return {Object} A reference to the augmented `targetObj` Object
11608 * @private
11609 */
11610 function shallowCopy (targetObj, srcObj) {
11611 each(srcObj, function (prop) {
11612 targetObj[prop] = srcObj[prop];
11613 });
11614
11615 return targetObj;
11616 }
11617
11618 /**
11619 * Copies each property from src onto target, but only if the property to
11620 * copy to target is undefined.
11621 * @param {Object} target Missing properties in this Object are filled in
11622 * @param {Object} src
11623 * @private
11624 */
11625 function defaults (target, src) {
11626 each(src, function (prop) {
11627 if (typeof target[prop] === 'undefined') {
11628 target[prop] = src[prop];
11629 }
11630 });
11631 }
11632
11633 /**
11634 * Calculates the interpolated tween values of an Object for a given
11635 * timestamp.
11636 * @param {Number} forPosition The position to compute the state for.
11637 * @param {Object} currentState Current state properties.
11638 * @param {Object} originalState: The original state properties the Object is
11639 * tweening from.
11640 * @param {Object} targetState: The destination state properties the Object
11641 * is tweening to.
11642 * @param {number} duration: The length of the tween in milliseconds.
11643 * @param {number} timestamp: The UNIX epoch time at which the tween began.
11644 * @param {Object} easing: This Object's keys must correspond to the keys in
11645 * targetState.
11646 * @private
11647 */
11648 function tweenProps (forPosition, currentState, originalState, targetState,
11649 duration, timestamp, easing) {
11650 var normalizedPosition =
11651 forPosition < timestamp ? 0 : (forPosition - timestamp) / duration;
11652
11653
11654 var prop;
11655 var easingObjectProp;
11656 var easingFn;
11657 for (prop in currentState) {
11658 if (currentState.hasOwnProperty(prop)) {
11659 easingObjectProp = easing[prop];
11660 easingFn = typeof easingObjectProp === 'function'
11661 ? easingObjectProp
11662 : formula[easingObjectProp];
11663
11664 currentState[prop] = tweenProp(
11665 originalState[prop],
11666 targetState[prop],
11667 easingFn,
11668 normalizedPosition
11669 );
11670 }
11671 }
11672
11673 return currentState;
11674 }
11675
11676 /**
11677 * Tweens a single property.
11678 * @param {number} start The value that the tween started from.
11679 * @param {number} end The value that the tween should end at.
11680 * @param {Function} easingFunc The easing curve to apply to the tween.
11681 * @param {number} position The normalized position (between 0.0 and 1.0) to
11682 * calculate the midpoint of 'start' and 'end' against.
11683 * @return {number} The tweened value.
11684 * @private
11685 */
11686 function tweenProp (start, end, easingFunc, position) {
11687 return start + (end - start) * easingFunc(position);
11688 }
11689
11690 /**
11691 * Applies a filter to NGTweenable instance.
11692 * @param {NGTweenable} tweenable The `NGTweenable` instance to call the filter
11693 * upon.
11694 * @param {String} filterName The name of the filter to apply.
11695 * @private
11696 */
11697 function applyFilter (tweenable, filterName) {
11698 var filters = NGTweenable.prototype.filter;
11699 var args = tweenable._filterArgs;
11700
11701 each(filters, function (name) {
11702 if (typeof filters[name][filterName] !== 'undefined') {
11703 filters[name][filterName].apply(tweenable, args);
11704 }
11705 });
11706 }
11707
11708 var timeoutHandler_endTime;
11709 var timeoutHandler_currentTime;
11710 var timeoutHandler_isEnded;
11711 var timeoutHandler_offset;
11712 /**
11713 * Handles the update logic for one step of a tween.
11714 * @param {NGTweenable} tweenable
11715 * @param {number} timestamp
11716 * @param {number} delay
11717 * @param {number} duration
11718 * @param {Object} currentState
11719 * @param {Object} originalState
11720 * @param {Object} targetState
11721 * @param {Object} easing
11722 * @param {Function(Object, *, number)} step
11723 * @param {Function(Function,number)}} schedule
11724 * @param {number=} opt_currentTimeOverride Needed for accurate timestamp in
11725 * NGTweenable#seek.
11726 * @private
11727 */
11728 function timeoutHandler (tweenable, timestamp, delay, duration, currentState,
11729 originalState, targetState, easing, step, schedule,
11730 opt_currentTimeOverride) {
11731
11732 timeoutHandler_endTime = timestamp + delay + duration;
11733
11734 timeoutHandler_currentTime =
11735 Math.min(opt_currentTimeOverride || now(), timeoutHandler_endTime);
11736
11737 timeoutHandler_isEnded =
11738 timeoutHandler_currentTime >= timeoutHandler_endTime;
11739
11740 timeoutHandler_offset = duration - (
11741 timeoutHandler_endTime - timeoutHandler_currentTime);
11742
11743 if (tweenable.isPlaying()) {
11744 if (timeoutHandler_isEnded) {
11745 step(targetState, tweenable._attachment, timeoutHandler_offset);
11746 tweenable.stop(true);
11747 } else {
11748 tweenable._scheduleId =
11749 schedule(tweenable._timeoutHandler, UPDATE_TIME);
11750
11751 applyFilter(tweenable, 'beforeTween');
11752
11753 // If the animation has not yet reached the start point (e.g., there was
11754 // delay that has not yet completed), just interpolate the starting
11755 // position of the tween.
11756 if (timeoutHandler_currentTime < (timestamp + delay)) {
11757 tweenProps(1, currentState, originalState, targetState, 1, 1, easing);
11758 } else {
11759 tweenProps(timeoutHandler_currentTime, currentState, originalState,
11760 targetState, duration, timestamp + delay, easing);
11761 }
11762
11763 applyFilter(tweenable, 'afterTween');
11764
11765 step(currentState, tweenable._attachment, timeoutHandler_offset);
11766 }
11767 }
11768 }
11769
11770
11771 /**
11772 * Creates a usable easing Object from a string, a function or another easing
11773 * Object. If `easing` is an Object, then this function clones it and fills
11774 * in the missing properties with `"linear"`.
11775 * @param {Object.<string|Function>} fromTweenParams
11776 * @param {Object|string|Function} easing
11777 * @return {Object.<string|Function>}
11778 * @private
11779 */
11780 function composeEasingObject (fromTweenParams, easing) {
11781 var composedEasing = {};
11782 var typeofEasing = typeof easing;
11783
11784 if (typeofEasing === 'string' || typeofEasing === 'function') {
11785 each(fromTweenParams, function (prop) {
11786 composedEasing[prop] = easing;
11787 });
11788 } else {
11789 each(fromTweenParams, function (prop) {
11790 if (!composedEasing[prop]) {
11791 composedEasing[prop] = easing[prop] || DEFAULT_EASING;
11792 }
11793 });
11794 }
11795
11796 return composedEasing;
11797 }
11798
11799 /**
11800 * NGTweenable constructor.
11801 * @class NGTweenable
11802 * @param {Object=} opt_initialState The values that the initial tween should
11803 * start at if a `from` object is not provided to `{{#crossLink
11804 * "NGTweenable/tween:method"}}{{/crossLink}}` or `{{#crossLink
11805 * "NGTweenable/setConfig:method"}}{{/crossLink}}`.
11806 * @param {Object=} opt_config Configuration object to be passed to
11807 * `{{#crossLink "NGTweenable/setConfig:method"}}{{/crossLink}}`.
11808 * @module NGTweenable
11809 * @constructor
11810 */
11811 function NGTweenable (opt_initialState, opt_config) {
11812 this._currentState = opt_initialState || {};
11813 this._configured = false;
11814 this._scheduleFunction = DEFAULT_SCHEDULE_FUNCTION;
11815
11816 // To prevent unnecessary calls to setConfig do not set default
11817 // configuration here. Only set default configuration immediately before
11818 // tweening if none has been set.
11819 if (typeof opt_config !== 'undefined') {
11820 this.setConfig(opt_config);
11821 }
11822 }
11823
11824 /**
11825 * Configure and start a tween.
11826 * @method tween
11827 * @param {Object=} opt_config Configuration object to be passed to
11828 * `{{#crossLink "NGTweenable/setConfig:method"}}{{/crossLink}}`.
11829 * @chainable
11830 */
11831 NGTweenable.prototype.tween = function (opt_config) {
11832 if (this._isTweening) {
11833 return this;
11834 }
11835
11836 // Only set default config if no configuration has been set previously and
11837 // none is provided now.
11838 if (opt_config !== undefined || !this._configured) {
11839 this.setConfig(opt_config);
11840 }
11841
11842 this._timestamp = now();
11843 this._start(this.get(), this._attachment);
11844 return this.resume();
11845 };
11846
11847 /**
11848 * Configure a tween that will start at some point in the future.
11849 *
11850 * @method setConfig
11851 * @param {Object} config The following values are valid:
11852 * - __from__ (_Object=_): Starting position. If omitted, `{{#crossLink
11853 * "NGTweenable/get:method"}}get(){{/crossLink}}` is used.
11854 * - __to__ (_Object=_): Ending position.
11855 * - __duration__ (_number=_): How many milliseconds to animate for.
11856 * - __delay__ (_delay=_): How many milliseconds to wait before starting the
11857 * tween.
11858 * - __start__ (_Function(Object, *)_): Function to execute when the tween
11859 * begins. Receives the state of the tween as the first parameter and
11860 * `attachment` as the second parameter.
11861 * - __step__ (_Function(Object, *, number)_): Function to execute on every
11862 * tick. Receives `{{#crossLink
11863 * "NGTweenable/get:method"}}get(){{/crossLink}}` as the first parameter,
11864 * `attachment` as the second parameter, and the time elapsed since the
11865 * start of the tween as the third. This function is not called on the
11866 * final step of the animation, but `finish` is.
11867 * - __finish__ (_Function(Object, *)_): Function to execute upon tween
11868 * completion. Receives the state of the tween as the first parameter and
11869 * `attachment` as the second parameter.
11870 * - __easing__ (_Object.<string|Function>|string|Function=_): Easing curve
11871 * name(s) or function(s) to use for the tween.
11872 * - __attachment__ (_*_): Cached value that is passed to the
11873 * `step`/`start`/`finish` methods.
11874 * @chainable
11875 */
11876 NGTweenable.prototype.setConfig = function (config) {
11877 config = config || {};
11878 this._configured = true;
11879
11880 // Attach something to this NGTweenable instance (e.g.: a DOM element, an
11881 // object, a string, etc.);
11882 this._attachment = config.attachment;
11883
11884 // Init the internal state
11885 this._pausedAtTime = null;
11886 this._scheduleId = null;
11887 this._delay = config.delay || 0;
11888 this._start = config.start || noop;
11889 this._step = config.step || noop;
11890 this._finish = config.finish || noop;
11891 this._duration = config.duration || DEFAULT_DURATION;
11892 this._currentState = shallowCopy({}, config.from || this.get());
11893 this._originalState = this.get();
11894 this._targetState = shallowCopy({}, config.to || this.get());
11895
11896 var self = this;
11897 this._timeoutHandler = function () {
11898 timeoutHandler(self,
11899 self._timestamp,
11900 self._delay,
11901 self._duration,
11902 self._currentState,
11903 self._originalState,
11904 self._targetState,
11905 self._easing,
11906 self._step,
11907 self._scheduleFunction
11908 );
11909 };
11910
11911 // Aliases used below
11912 var currentState = this._currentState;
11913 var targetState = this._targetState;
11914
11915 // Ensure that there is always something to tween to.
11916 defaults(targetState, currentState);
11917
11918 this._easing = composeEasingObject(
11919 currentState, config.easing || DEFAULT_EASING);
11920
11921 this._filterArgs =
11922 [currentState, this._originalState, targetState, this._easing];
11923
11924 applyFilter(this, 'tweenCreated');
11925 return this;
11926 };
11927
11928 /**
11929 * @method get
11930 * @return {Object} The current state.
11931 */
11932 NGTweenable.prototype.get = function () {
11933 return shallowCopy({}, this._currentState);
11934 };
11935
11936 /**
11937 * @method set
11938 * @param {Object} state The current state.
11939 */
11940 NGTweenable.prototype.set = function (state) {
11941 this._currentState = state;
11942 };
11943
11944 /**
11945 * Pause a tween. Paused tweens can be resumed from the point at which they
11946 * were paused. This is different from `{{#crossLink
11947 * "NGTweenable/stop:method"}}{{/crossLink}}`, as that method
11948 * causes a tween to start over when it is resumed.
11949 * @method pause
11950 * @chainable
11951 */
11952 NGTweenable.prototype.pause = function () {
11953 this._pausedAtTime = now();
11954 this._isPaused = true;
11955 return this;
11956 };
11957
11958 /**
11959 * Resume a paused tween.
11960 * @method resume
11961 * @chainable
11962 */
11963 NGTweenable.prototype.resume = function () {
11964 if (this._isPaused) {
11965 this._timestamp += now() - this._pausedAtTime;
11966 }
11967
11968 this._isPaused = false;
11969 this._isTweening = true;
11970
11971 this._timeoutHandler();
11972
11973 return this;
11974 };
11975
11976 /**
11977 * Move the state of the animation to a specific point in the tween's
11978 * timeline. If the animation is not running, this will cause the `step`
11979 * handlers to be called.
11980 * @method seek
11981 * @param {millisecond} millisecond The millisecond of the animation to seek
11982 * to. This must not be less than `0`.
11983 * @chainable
11984 */
11985 NGTweenable.prototype.seek = function (millisecond) {
11986 millisecond = Math.max(millisecond, 0);
11987 var currentTime = now();
11988
11989 if ((this._timestamp + millisecond) === 0) {
11990 return this;
11991 }
11992
11993 this._timestamp = currentTime - millisecond;
11994
11995 if (!this.isPlaying()) {
11996 this._isTweening = true;
11997 this._isPaused = false;
11998
11999 // If the animation is not running, call timeoutHandler to make sure that
12000 // any step handlers are run.
12001 timeoutHandler(this,
12002 this._timestamp,
12003 this._delay,
12004 this._duration,
12005 this._currentState,
12006 this._originalState,
12007 this._targetState,
12008 this._easing,
12009 this._step,
12010 this._scheduleFunction,
12011 currentTime
12012 );
12013
12014 this.pause();
12015 }
12016
12017 return this;
12018 };
12019
12020 /**
12021 * Stops and cancels a tween.
12022 * @param {boolean=} gotoEnd If `false` or omitted, the tween just stops at
12023 * its current state, and the `finish` handler is not invoked. If `true`,
12024 * the tweened object's values are instantly set to the target values, and
12025 * `finish` is invoked.
12026 * @method stop
12027 * @chainable
12028 */
12029 NGTweenable.prototype.stop = function (gotoEnd) {
12030 this._isTweening = false;
12031 this._isPaused = false;
12032 this._timeoutHandler = noop;
12033
12034 (root.cancelAnimationFrame ||
12035 root.webkitCancelAnimationFrame ||
12036 root.oCancelAnimationFrame ||
12037 root.msCancelAnimationFrame ||
12038 root.mozCancelRequestAnimationFrame ||
12039 root.clearTimeout)(this._scheduleId);
12040
12041 if (gotoEnd) {
12042 applyFilter(this, 'beforeTween');
12043 tweenProps(
12044 1,
12045 this._currentState,
12046 this._originalState,
12047 this._targetState,
12048 1,
12049 0,
12050 this._easing
12051 );
12052 applyFilter(this, 'afterTween');
12053 applyFilter(this, 'afterTweenEnd');
12054 this._finish.call(this, this._currentState, this._attachment);
12055 }
12056
12057 return this;
12058 };
12059
12060 /**
12061 * @method isPlaying
12062 * @return {boolean} Whether or not a tween is running.
12063 */
12064 NGTweenable.prototype.isPlaying = function () {
12065 return this._isTweening && !this._isPaused;
12066 };
12067
12068 /**
12069 * Set a custom schedule function.
12070 *
12071 * If a custom function is not set,
12072 * [`requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/Web/API/window.requestAnimationFrame)
12073 * is used if available, otherwise
12074 * [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/Window.setTimeout)
12075 * is used.
12076 * @method setScheduleFunction
12077 * @param {Function(Function,number)} scheduleFunction The function to be
12078 * used to schedule the next frame to be rendered.
12079 */
12080 NGTweenable.prototype.setScheduleFunction = function (scheduleFunction) {
12081 this._scheduleFunction = scheduleFunction;
12082 };
12083
12084 /**
12085 * `delete` all "own" properties. Call this when the `NGTweenable` instance
12086 * is no longer needed to free memory.
12087 * @method dispose
12088 */
12089 NGTweenable.prototype.dispose = function () {
12090 var prop;
12091 for (prop in this) {
12092 if (this.hasOwnProperty(prop)) {
12093 delete this[prop];
12094 }
12095 }
12096 };
12097
12098 /**
12099 * Filters are used for transforming the properties of a tween at various
12100 * points in a NGTweenable's life cycle. See the README for more info on this.
12101 * @private
12102 */
12103 NGTweenable.prototype.filter = {};
12104
12105 /**
12106 * This object contains all of the tweens available to Shifty. It is
12107 * extensible - simply attach properties to the `NGTweenable.prototype.formula`
12108 * Object following the same format as `linear`.
12109 *
12110 * `pos` should be a normalized `number` (between 0 and 1).
12111 * @property formula
12112 * @type {Object(function)}
12113 */
12114 NGTweenable.prototype.formula = {
12115 linear: function (pos) {
12116 return pos;
12117 }
12118 };
12119
12120 formula = NGTweenable.prototype.formula;
12121
12122 shallowCopy(NGTweenable, {
12123 'now': now
12124 ,'each': each
12125 ,'tweenProps': tweenProps
12126 ,'tweenProp': tweenProp
12127 ,'applyFilter': applyFilter
12128 ,'shallowCopy': shallowCopy
12129 ,'defaults': defaults
12130 ,'composeEasingObject': composeEasingObject
12131 });
12132
12133 // `root` is provided in the intro/outro files.
12134
12135 // A hook used for unit testing.
12136 if (typeof SHIFTY_DEBUG_NOW === 'function') {
12137 root.timeoutHandler = timeoutHandler;
12138 }
12139
12140 // Bootstrap NGTweenable appropriately for the environment.
12141 if (typeof exports === 'object') {
12142 // CommonJS
12143 module.exports = NGTweenable;
12144 } else if (typeof define === 'function' && define.amdDISABLED) {
12145 // AMD
12146 define(function () {return NGTweenable;});
12147 } else if (typeof root.NGTweenable === 'undefined') {
12148 // Browser: Make `NGTweenable` globally accessible.
12149 root.NGTweenable = NGTweenable;
12150 }
12151
12152 return NGTweenable;
12153
12154} ());
12155
12156/*!
12157 * All equations are adapted from Thomas Fuchs'
12158 * [Scripty2](https://github.com/madrobby/scripty2/blob/master/src/effects/transitions/penner.js).
12159 *
12160 * Based on Easing Equations (c) 2003 [Robert
12161 * Penner](http://www.robertpenner.com/), all rights reserved. This work is
12162 * [subject to terms](http://www.robertpenner.com/easing_terms_of_use.html).
12163 */
12164
12165/*!
12166 * TERMS OF USE - EASING EQUATIONS
12167 * Open source under the BSD License.
12168 * Easing Equations (c) 2003 Robert Penner, all rights reserved.
12169 */
12170
12171;(function () {
12172
12173 NGTweenable.shallowCopy(NGTweenable.prototype.formula, {
12174 easeInQuad: function (pos) {
12175 return Math.pow(pos, 2);
12176 },
12177
12178 easeOutQuad: function (pos) {
12179 return -(Math.pow((pos - 1), 2) - 1);
12180 },
12181
12182 easeInOutQuad: function (pos) {
12183 if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,2);}
12184 return -0.5 * ((pos -= 2) * pos - 2);
12185 },
12186
12187 easeInCubic: function (pos) {
12188 return Math.pow(pos, 3);
12189 },
12190
12191 easeOutCubic: function (pos) {
12192 return (Math.pow((pos - 1), 3) + 1);
12193 },
12194
12195 easeInOutCubic: function (pos) {
12196 if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,3);}
12197 return 0.5 * (Math.pow((pos - 2),3) + 2);
12198 },
12199
12200 easeInQuart: function (pos) {
12201 return Math.pow(pos, 4);
12202 },
12203
12204 easeOutQuart: function (pos) {
12205 return -(Math.pow((pos - 1), 4) - 1);
12206 },
12207
12208 easeInOutQuart: function (pos) {
12209 if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,4);}
12210 return -0.5 * ((pos -= 2) * Math.pow(pos,3) - 2);
12211 },
12212
12213 easeInQuint: function (pos) {
12214 return Math.pow(pos, 5);
12215 },
12216
12217 easeOutQuint: function (pos) {
12218 return (Math.pow((pos - 1), 5) + 1);
12219 },
12220
12221 easeInOutQuint: function (pos) {
12222 if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,5);}
12223 return 0.5 * (Math.pow((pos - 2),5) + 2);
12224 },
12225
12226 easeInSine: function (pos) {
12227 return -Math.cos(pos * (Math.PI / 2)) + 1;
12228 },
12229
12230 easeOutSine: function (pos) {
12231 return Math.sin(pos * (Math.PI / 2));
12232 },
12233
12234 easeInOutSine: function (pos) {
12235 return (-0.5 * (Math.cos(Math.PI * pos) - 1));
12236 },
12237
12238 easeInExpo: function (pos) {
12239 return (pos === 0) ? 0 : Math.pow(2, 10 * (pos - 1));
12240 },
12241
12242 easeOutExpo: function (pos) {
12243 return (pos === 1) ? 1 : -Math.pow(2, -10 * pos) + 1;
12244 },
12245
12246 easeInOutExpo: function (pos) {
12247 if (pos === 0) {return 0;}
12248 if (pos === 1) {return 1;}
12249 if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(2,10 * (pos - 1));}
12250 return 0.5 * (-Math.pow(2, -10 * --pos) + 2);
12251 },
12252
12253 easeInCirc: function (pos) {
12254 return -(Math.sqrt(1 - (pos * pos)) - 1);
12255 },
12256
12257 easeOutCirc: function (pos) {
12258 return Math.sqrt(1 - Math.pow((pos - 1), 2));
12259 },
12260
12261 easeInOutCirc: function (pos) {
12262 if ((pos /= 0.5) < 1) {return -0.5 * (Math.sqrt(1 - pos * pos) - 1);}
12263 return 0.5 * (Math.sqrt(1 - (pos -= 2) * pos) + 1);
12264 },
12265
12266 easeOutBounce: function (pos) {
12267 if ((pos) < (1 / 2.75)) {
12268 return (7.5625 * pos * pos);
12269 } else if (pos < (2 / 2.75)) {
12270 return (7.5625 * (pos -= (1.5 / 2.75)) * pos + 0.75);
12271 } else if (pos < (2.5 / 2.75)) {
12272 return (7.5625 * (pos -= (2.25 / 2.75)) * pos + 0.9375);
12273 } else {
12274 return (7.5625 * (pos -= (2.625 / 2.75)) * pos + 0.984375);
12275 }
12276 },
12277
12278 easeInBack: function (pos) {
12279 var s = 1.70158;
12280 return (pos) * pos * ((s + 1) * pos - s);
12281 },
12282
12283 easeOutBack: function (pos) {
12284 var s = 1.70158;
12285 return (pos = pos - 1) * pos * ((s + 1) * pos + s) + 1;
12286 },
12287
12288 easeInOutBack: function (pos) {
12289 var s = 1.70158;
12290 if ((pos /= 0.5) < 1) {
12291 return 0.5 * (pos * pos * (((s *= (1.525)) + 1) * pos - s));
12292 }
12293 return 0.5 * ((pos -= 2) * pos * (((s *= (1.525)) + 1) * pos + s) + 2);
12294 },
12295
12296 elastic: function (pos) {
12297 // jshint maxlen:90
12298 return -1 * Math.pow(4,-8 * pos) * Math.sin((pos * 6 - 1) * (2 * Math.PI) / 2) + 1;
12299 },
12300
12301 swingFromTo: function (pos) {
12302 var s = 1.70158;
12303 return ((pos /= 0.5) < 1) ?
12304 0.5 * (pos * pos * (((s *= (1.525)) + 1) * pos - s)) :
12305 0.5 * ((pos -= 2) * pos * (((s *= (1.525)) + 1) * pos + s) + 2);
12306 },
12307
12308 swingFrom: function (pos) {
12309 var s = 1.70158;
12310 return pos * pos * ((s + 1) * pos - s);
12311 },
12312
12313 swingTo: function (pos) {
12314 var s = 1.70158;
12315 return (pos -= 1) * pos * ((s + 1) * pos + s) + 1;
12316 },
12317
12318 bounce: function (pos) {
12319 if (pos < (1 / 2.75)) {
12320 return (7.5625 * pos * pos);
12321 } else if (pos < (2 / 2.75)) {
12322 return (7.5625 * (pos -= (1.5 / 2.75)) * pos + 0.75);
12323 } else if (pos < (2.5 / 2.75)) {
12324 return (7.5625 * (pos -= (2.25 / 2.75)) * pos + 0.9375);
12325 } else {
12326 return (7.5625 * (pos -= (2.625 / 2.75)) * pos + 0.984375);
12327 }
12328 },
12329
12330 bouncePast: function (pos) {
12331 if (pos < (1 / 2.75)) {
12332 return (7.5625 * pos * pos);
12333 } else if (pos < (2 / 2.75)) {
12334 return 2 - (7.5625 * (pos -= (1.5 / 2.75)) * pos + 0.75);
12335 } else if (pos < (2.5 / 2.75)) {
12336 return 2 - (7.5625 * (pos -= (2.25 / 2.75)) * pos + 0.9375);
12337 } else {
12338 return 2 - (7.5625 * (pos -= (2.625 / 2.75)) * pos + 0.984375);
12339 }
12340 },
12341
12342 easeFromTo: function (pos) {
12343 if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,4);}
12344 return -0.5 * ((pos -= 2) * Math.pow(pos,3) - 2);
12345 },
12346
12347 easeFrom: function (pos) {
12348 return Math.pow(pos,4);
12349 },
12350
12351 easeTo: function (pos) {
12352 return Math.pow(pos,0.25);
12353 }
12354 });
12355
12356}());
12357
12358// jshint maxlen:100
12359/**
12360 * The Bezier magic in this file is adapted/copied almost wholesale from
12361 * [Scripty2](https://github.com/madrobby/scripty2/blob/master/src/effects/transitions/cubic-bezier.js),
12362 * which was adapted from Apple code (which probably came from
12363 * [here](http://opensource.apple.com/source/WebCore/WebCore-955.66/platform/graphics/UnitBezier.h)).
12364 * Special thanks to Apple and Thomas Fuchs for much of this code.
12365 */
12366
12367/**
12368 * Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
12369 *
12370 * Redistribution and use in source and binary forms, with or without
12371 * modification, are permitted provided that the following conditions are met:
12372 *
12373 * 1. Redistributions of source code must retain the above copyright notice,
12374 * this list of conditions and the following disclaimer.
12375 *
12376 * 2. Redistributions in binary form must reproduce the above copyright notice,
12377 * this list of conditions and the following disclaimer in the documentation
12378 * and/or other materials provided with the distribution.
12379 *
12380 * 3. Neither the name of the copyright holder(s) nor the names of any
12381 * contributors may be used to endorse or promote products derived from
12382 * this software without specific prior written permission.
12383 *
12384 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
12385 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
12386 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
12387 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
12388 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
12389 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
12390 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
12391 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
12392 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
12393 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
12394 * POSSIBILITY OF SUCH DAMAGE.
12395 */
12396;(function () {
12397 // port of webkit cubic bezier handling by http://www.netzgesta.de/dev/
12398 function cubicBezierAtTime(t,p1x,p1y,p2x,p2y,duration) {
12399 var ax = 0,bx = 0,cx = 0,ay = 0,by = 0,cy = 0;
12400 function sampleCurveX(t) {
12401 return ((ax * t + bx) * t + cx) * t;
12402 }
12403 function sampleCurveY(t) {
12404 return ((ay * t + by) * t + cy) * t;
12405 }
12406 function sampleCurveDerivativeX(t) {
12407 return (3.0 * ax * t + 2.0 * bx) * t + cx;
12408 }
12409 function solveEpsilon(duration) {
12410 return 1.0 / (200.0 * duration);
12411 }
12412 function solve(x,epsilon) {
12413 return sampleCurveY(solveCurveX(x, epsilon));
12414 }
12415 function fabs(n) {
12416 if (n >= 0) {
12417 return n;
12418 } else {
12419 return 0 - n;
12420 }
12421 }
12422 function solveCurveX(x, epsilon) {
12423 var t0,t1,t2,x2,d2,i;
12424 for (t2 = x, i = 0; i < 8; i++) {
12425 x2 = sampleCurveX(t2) - x;
12426 if (fabs(x2) < epsilon) {
12427 return t2;
12428 }
12429 d2 = sampleCurveDerivativeX(t2);
12430 if (fabs(d2) < 1e-6) {
12431 break;
12432 }
12433 t2 = t2 - x2 / d2;
12434 }
12435 t0 = 0.0;
12436 t1 = 1.0;
12437 t2 = x;
12438 if (t2 < t0) {
12439 return t0;
12440 }
12441 if (t2 > t1) {
12442 return t1;
12443 }
12444 while (t0 < t1) {
12445 x2 = sampleCurveX(t2);
12446 if (fabs(x2 - x) < epsilon) {
12447 return t2;
12448 }
12449 if (x > x2) {
12450 t0 = t2;
12451 }else {
12452 t1 = t2;
12453 }
12454 t2 = (t1 - t0) * 0.5 + t0;
12455 }
12456 return t2; // Failure.
12457 }
12458 cx = 3.0 * p1x;
12459 bx = 3.0 * (p2x - p1x) - cx;
12460 ax = 1.0 - cx - bx;
12461 cy = 3.0 * p1y;
12462 by = 3.0 * (p2y - p1y) - cy;
12463 ay = 1.0 - cy - by;
12464 return solve(t, solveEpsilon(duration));
12465 }
12466 /**
12467 * getCubicBezierTransition(x1, y1, x2, y2) -> Function
12468 *
12469 * Generates a transition easing function that is compatible
12470 * with WebKit's CSS transitions `-webkit-transition-timing-function`
12471 * CSS property.
12472 *
12473 * The W3C has more information about CSS3 transition timing functions:
12474 * http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag
12475 *
12476 * @param {number} x1
12477 * @param {number} y1
12478 * @param {number} x2
12479 * @param {number} y2
12480 * @return {function}
12481 * @private
12482 */
12483 function getCubicBezierTransition (x1, y1, x2, y2) {
12484 return function (pos) {
12485 return cubicBezierAtTime(pos,x1,y1,x2,y2,1);
12486 };
12487 }
12488 // End ported code
12489
12490 /**
12491 * Create a Bezier easing function and attach it to `{{#crossLink
12492 * "NGTweenable/formula:property"}}NGTweenable#formula{{/crossLink}}`. This
12493 * function gives you total control over the easing curve. Matthew Lein's
12494 * [Ceaser](http://matthewlein.com/ceaser/) is a useful tool for visualizing
12495 * the curves you can make with this function.
12496 * @method setBezierFunction
12497 * @param {string} name The name of the easing curve. Overwrites the old
12498 * easing function on `{{#crossLink
12499 * "NGTweenable/formula:property"}}NGTweenable#formula{{/crossLink}}` if it
12500 * exists.
12501 * @param {number} x1
12502 * @param {number} y1
12503 * @param {number} x2
12504 * @param {number} y2
12505 * @return {function} The easing function that was attached to
12506 * NGTweenable.prototype.formula.
12507 */
12508 NGTweenable.setBezierFunction = function (name, x1, y1, x2, y2) {
12509 var cubicBezierTransition = getCubicBezierTransition(x1, y1, x2, y2);
12510 cubicBezierTransition.displayName = name;
12511 cubicBezierTransition.x1 = x1;
12512 cubicBezierTransition.y1 = y1;
12513 cubicBezierTransition.x2 = x2;
12514 cubicBezierTransition.y2 = y2;
12515
12516 return NGTweenable.prototype.formula[name] = cubicBezierTransition;
12517 };
12518
12519
12520 /**
12521 * `delete` an easing function from `{{#crossLink
12522 * "NGTweenable/formula:property"}}NGTweenable#formula{{/crossLink}}`. Be
12523 * careful with this method, as it `delete`s whatever easing formula matches
12524 * `name` (which means you can delete standard Shifty easing functions).
12525 * @method unsetBezierFunction
12526 * @param {string} name The name of the easing function to delete.
12527 * @return {function}
12528 */
12529 NGTweenable.unsetBezierFunction = function (name) {
12530 delete NGTweenable.prototype.formula[name];
12531 };
12532
12533})();
12534
12535;(function () {
12536
12537 function getInterpolatedValues (
12538 from, current, targetState, position, easing, delay) {
12539 return NGTweenable.tweenProps(
12540 position, current, from, targetState, 1, delay, easing);
12541 }
12542
12543 // Fake a NGTweenable and patch some internals. This approach allows us to
12544 // skip uneccessary processing and object recreation, cutting down on garbage
12545 // collection pauses.
12546 var mockNGTweenable = new NGTweenable();
12547 mockNGTweenable._filterArgs = [];
12548
12549 /**
12550 * Compute the midpoint of two Objects. This method effectively calculates a
12551 * specific frame of animation that `{{#crossLink
12552 * "NGTweenable/tween:method"}}{{/crossLink}}` does many times over the course
12553 * of a full tween.
12554 *
12555 * var interpolatedValues = NGTweenable.interpolate({
12556 * width: '100px',
12557 * opacity: 0,
12558 * color: '#fff'
12559 * }, {
12560 * width: '200px',
12561 * opacity: 1,
12562 * color: '#000'
12563 * }, 0.5);
12564 *
12565 * console.log(interpolatedValues);
12566 * // {opacity: 0.5, width: "150px", color: "rgb(127,127,127)"}
12567 *
12568 * @static
12569 * @method interpolate
12570 * @param {Object} from The starting values to tween from.
12571 * @param {Object} targetState The ending values to tween to.
12572 * @param {number} position The normalized position value (between `0.0` and
12573 * `1.0`) to interpolate the values between `from` and `to` for. `from`
12574 * represents `0` and `to` represents `1`.
12575 * @param {Object.<string|Function>|string|Function} easing The easing
12576 * curve(s) to calculate the midpoint against. You can reference any easing
12577 * function attached to `NGTweenable.prototype.formula`, or provide the easing
12578 * function(s) directly. If omitted, this defaults to "linear".
12579 * @param {number=} opt_delay Optional delay to pad the beginning of the
12580 * interpolated tween with. This increases the range of `position` from (`0`
12581 * through `1`) to (`0` through `1 + opt_delay`). So, a delay of `0.5` would
12582 * increase all valid values of `position` to numbers between `0` and `1.5`.
12583 * @return {Object}
12584 */
12585 NGTweenable.interpolate = function (
12586 from, targetState, position, easing, opt_delay) {
12587
12588 var current = NGTweenable.shallowCopy({}, from);
12589 var delay = opt_delay || 0;
12590 var easingObject = NGTweenable.composeEasingObject(
12591 from, easing || 'linear');
12592
12593 mockNGTweenable.set({});
12594
12595 // Alias and reuse the _filterArgs array instead of recreating it.
12596 var filterArgs = mockNGTweenable._filterArgs;
12597 filterArgs.length = 0;
12598 filterArgs[0] = current;
12599 filterArgs[1] = from;
12600 filterArgs[2] = targetState;
12601 filterArgs[3] = easingObject;
12602
12603 // Any defined value transformation must be applied
12604 NGTweenable.applyFilter(mockNGTweenable, 'tweenCreated');
12605 NGTweenable.applyFilter(mockNGTweenable, 'beforeTween');
12606
12607 var interpolatedValues = getInterpolatedValues(
12608 from, current, targetState, position, easingObject, delay);
12609
12610 // Transform values back into their original format
12611 NGTweenable.applyFilter(mockNGTweenable, 'afterTween');
12612
12613 return interpolatedValues;
12614 };
12615
12616}());
12617
12618/**
12619 * This module adds string interpolation support to Shifty.
12620 *
12621 * The Token extension allows Shifty to tween numbers inside of strings. Among
12622 * other things, this allows you to animate CSS properties. For example, you
12623 * can do this:
12624 *
12625 * var tweenable = new NGTweenable();
12626 * tweenable.tween({
12627 * from: { transform: 'translateX(45px)' },
12628 * to: { transform: 'translateX(90xp)' }
12629 * });
12630 *
12631 * `translateX(45)` will be tweened to `translateX(90)`. To demonstrate:
12632 *
12633 * var tweenable = new NGTweenable();
12634 * tweenable.tween({
12635 * from: { transform: 'translateX(45px)' },
12636 * to: { transform: 'translateX(90px)' },
12637 * step: function (state) {
12638 * console.log(state.transform);
12639 * }
12640 * });
12641 *
12642 * The above snippet will log something like this in the console:
12643 *
12644 * translateX(60.3px)
12645 * ...
12646 * translateX(76.05px)
12647 * ...
12648 * translateX(90px)
12649 *
12650 * Another use for this is animating colors:
12651 *
12652 * var tweenable = new NGTweenable();
12653 * tweenable.tween({
12654 * from: { color: 'rgb(0,255,0)' },
12655 * to: { color: 'rgb(255,0,255)' },
12656 * step: function (state) {
12657 * console.log(state.color);
12658 * }
12659 * });
12660 *
12661 * The above snippet will log something like this:
12662 *
12663 * rgb(84,170,84)
12664 * ...
12665 * rgb(170,84,170)
12666 * ...
12667 * rgb(255,0,255)
12668 *
12669 * This extension also supports hexadecimal colors, in both long (`#ff00ff`)
12670 * and short (`#f0f`) forms. Be aware that hexadecimal input values will be
12671 * converted into the equivalent RGB output values. This is done to optimize
12672 * for performance.
12673 *
12674 * var tweenable = new NGTweenable();
12675 * tweenable.tween({
12676 * from: { color: '#0f0' },
12677 * to: { color: '#f0f' },
12678 * step: function (state) {
12679 * console.log(state.color);
12680 * }
12681 * });
12682 *
12683 * This snippet will generate the same output as the one before it because
12684 * equivalent values were supplied (just in hexadecimal form rather than RGB):
12685 *
12686 * rgb(84,170,84)
12687 * ...
12688 * rgb(170,84,170)
12689 * ...
12690 * rgb(255,0,255)
12691 *
12692 * ## Easing support
12693 *
12694 * Easing works somewhat differently in the Token extension. This is because
12695 * some CSS properties have multiple values in them, and you might need to
12696 * tween each value along its own easing curve. A basic example:
12697 *
12698 * var tweenable = new NGTweenable();
12699 * tweenable.tween({
12700 * from: { transform: 'translateX(0px) translateY(0px)' },
12701 * to: { transform: 'translateX(100px) translateY(100px)' },
12702 * easing: { transform: 'easeInQuad' },
12703 * step: function (state) {
12704 * console.log(state.transform);
12705 * }
12706 * });
12707 *
12708 * The above snippet will create values like this:
12709 *
12710 * translateX(11.56px) translateY(11.56px)
12711 * ...
12712 * translateX(46.24px) translateY(46.24px)
12713 * ...
12714 * translateX(100px) translateY(100px)
12715 *
12716 * In this case, the values for `translateX` and `translateY` are always the
12717 * same for each step of the tween, because they have the same start and end
12718 * points and both use the same easing curve. We can also tween `translateX`
12719 * and `translateY` along independent curves:
12720 *
12721 * var tweenable = new NGTweenable();
12722 * tweenable.tween({
12723 * from: { transform: 'translateX(0px) translateY(0px)' },
12724 * to: { transform: 'translateX(100px) translateY(100px)' },
12725 * easing: { transform: 'easeInQuad bounce' },
12726 * step: function (state) {
12727 * console.log(state.transform);
12728 * }
12729 * });
12730 *
12731 * The above snippet will create values like this:
12732 *
12733 * translateX(10.89px) translateY(82.35px)
12734 * ...
12735 * translateX(44.89px) translateY(86.73px)
12736 * ...
12737 * translateX(100px) translateY(100px)
12738 *
12739 * `translateX` and `translateY` are not in sync anymore, because `easeInQuad`
12740 * was specified for `translateX` and `bounce` for `translateY`. Mixing and
12741 * matching easing curves can make for some interesting motion in your
12742 * animations.
12743 *
12744 * The order of the space-separated easing curves correspond the token values
12745 * they apply to. If there are more token values than easing curves listed,
12746 * the last easing curve listed is used.
12747 * @submodule NGTweenable.token
12748 */
12749
12750// token function is defined above only so that dox-foundation sees it as
12751// documentation and renders it. It is never used, and is optimized away at
12752// build time.
12753
12754;(function (NGTweenable) {
12755
12756 /**
12757 * @typedef {{
12758 * formatString: string
12759 * chunkNames: Array.<string>
12760 * }}
12761 * @private
12762 */
12763 var formatManifest;
12764
12765 // CONSTANTS
12766
12767 var R_NUMBER_COMPONENT = /(\d|\-|\.)/;
12768 var R_FORMAT_CHUNKS = /([^\-0-9\.]+)/g;
12769 var R_UNFORMATTED_VALUES = /[0-9.\-]+/g;
12770 var R_RGB = new RegExp(
12771 'rgb\\(' + R_UNFORMATTED_VALUES.source +
12772 (/,\s*/.source) + R_UNFORMATTED_VALUES.source +
12773 (/,\s*/.source) + R_UNFORMATTED_VALUES.source + '\\)', 'g');
12774 var R_RGB_PREFIX = /^.*\(/;
12775 var R_HEX = /#([0-9]|[a-f]){3,6}/gi;
12776 var VALUE_PLACEHOLDER = 'VAL';
12777
12778 // HELPERS
12779
12780 /**
12781 * @param {Array.number} rawValues
12782 * @param {string} prefix
12783 *
12784 * @return {Array.<string>}
12785 * @private
12786 */
12787 function getFormatChunksFrom (rawValues, prefix) {
12788 var accumulator = [];
12789
12790 var rawValuesLength = rawValues.length;
12791 var i;
12792
12793 for (i = 0; i < rawValuesLength; i++) {
12794 accumulator.push('_' + prefix + '_' + i);
12795 }
12796
12797 return accumulator;
12798 }
12799
12800 /**
12801 * @param {string} formattedString
12802 *
12803 * @return {string}
12804 * @private
12805 */
12806 function getFormatStringFrom (formattedString) {
12807 var chunks = formattedString.match(R_FORMAT_CHUNKS);
12808
12809 if (!chunks) {
12810 // chunks will be null if there were no tokens to parse in
12811 // formattedString (for example, if formattedString is '2'). Coerce
12812 // chunks to be useful here.
12813 chunks = ['', ''];
12814
12815 // If there is only one chunk, assume that the string is a number
12816 // followed by a token...
12817 // NOTE: This may be an unwise assumption.
12818 } else if (chunks.length === 1 ||
12819 // ...or if the string starts with a number component (".", "-", or a
12820 // digit)...
12821 formattedString.charAt(0).match(R_NUMBER_COMPONENT)) {
12822 // ...prepend an empty string here to make sure that the formatted number
12823 // is properly replaced by VALUE_PLACEHOLDER
12824 chunks.unshift('');
12825 }
12826
12827 return chunks.join(VALUE_PLACEHOLDER);
12828 }
12829
12830 /**
12831 * Convert all hex color values within a string to an rgb string.
12832 *
12833 * @param {Object} stateObject
12834 *
12835 * @return {Object} The modified obj
12836 * @private
12837 */
12838 function sanitizeObjectForHexProps (stateObject) {
12839 NGTweenable.each(stateObject, function (prop) {
12840 var currentProp = stateObject[prop];
12841
12842 if (typeof currentProp === 'string' && currentProp.match(R_HEX)) {
12843 stateObject[prop] = sanitizeHexChunksToRGB(currentProp);
12844 }
12845 });
12846 }
12847
12848 /**
12849 * @param {string} str
12850 *
12851 * @return {string}
12852 * @private
12853 */
12854 function sanitizeHexChunksToRGB (str) {
12855 return filterStringChunks(R_HEX, str, convertHexToRGB);
12856 }
12857
12858 /**
12859 * @param {string} hexString
12860 *
12861 * @return {string}
12862 * @private
12863 */
12864 function convertHexToRGB (hexString) {
12865 var rgbArr = hexToRGBArray(hexString);
12866 return 'rgb(' + rgbArr[0] + ',' + rgbArr[1] + ',' + rgbArr[2] + ')';
12867 }
12868
12869 var hexToRGBArray_returnArray = [];
12870 /**
12871 * Convert a hexadecimal string to an array with three items, one each for
12872 * the red, blue, and green decimal values.
12873 *
12874 * @param {string} hex A hexadecimal string.
12875 *
12876 * @returns {Array.<number>} The converted Array of RGB values if `hex` is a
12877 * valid string, or an Array of three 0's.
12878 * @private
12879 */
12880 function hexToRGBArray (hex) {
12881
12882 hex = hex.replace(/#/, '');
12883
12884 // If the string is a shorthand three digit hex notation, normalize it to
12885 // the standard six digit notation
12886 if (hex.length === 3) {
12887 hex = hex.split('');
12888 hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
12889 }
12890
12891 hexToRGBArray_returnArray[0] = hexToDec(hex.substr(0, 2));
12892 hexToRGBArray_returnArray[1] = hexToDec(hex.substr(2, 2));
12893 hexToRGBArray_returnArray[2] = hexToDec(hex.substr(4, 2));
12894
12895 return hexToRGBArray_returnArray;
12896 }
12897
12898 /**
12899 * Convert a base-16 number to base-10.
12900 *
12901 * @param {Number|String} hex The value to convert
12902 *
12903 * @returns {Number} The base-10 equivalent of `hex`.
12904 * @private
12905 */
12906 function hexToDec (hex) {
12907 return parseInt(hex, 16);
12908 }
12909
12910 /**
12911 * Runs a filter operation on all chunks of a string that match a RegExp
12912 *
12913 * @param {RegExp} pattern
12914 * @param {string} unfilteredString
12915 * @param {function(string)} filter
12916 *
12917 * @return {string}
12918 * @private
12919 */
12920 function filterStringChunks (pattern, unfilteredString, filter) {
12921 var pattenMatches = unfilteredString.match(pattern);
12922 var filteredString = unfilteredString.replace(pattern, VALUE_PLACEHOLDER);
12923
12924 if (pattenMatches) {
12925 var pattenMatchesLength = pattenMatches.length;
12926 var currentChunk;
12927
12928 for (var i = 0; i < pattenMatchesLength; i++) {
12929 currentChunk = pattenMatches.shift();
12930 filteredString = filteredString.replace(
12931 VALUE_PLACEHOLDER, filter(currentChunk));
12932 }
12933 }
12934
12935 return filteredString;
12936 }
12937
12938 /**
12939 * Check for floating point values within rgb strings and rounds them.
12940 *
12941 * @param {string} formattedString
12942 *
12943 * @return {string}
12944 * @private
12945 */
12946 function sanitizeRGBChunks (formattedString) {
12947 return filterStringChunks(R_RGB, formattedString, sanitizeRGBChunk);
12948 }
12949
12950 /**
12951 * @param {string} rgbChunk
12952 *
12953 * @return {string}
12954 * @private
12955 */
12956 function sanitizeRGBChunk (rgbChunk) {
12957 var numbers = rgbChunk.match(R_UNFORMATTED_VALUES);
12958 var numbersLength = numbers.length;
12959 var sanitizedString = rgbChunk.match(R_RGB_PREFIX)[0];
12960
12961 for (var i = 0; i < numbersLength; i++) {
12962 sanitizedString += parseInt(numbers[i], 10) + ',';
12963 }
12964
12965 sanitizedString = sanitizedString.slice(0, -1) + ')';
12966
12967 return sanitizedString;
12968 }
12969
12970 /**
12971 * @param {Object} stateObject
12972 *
12973 * @return {Object} An Object of formatManifests that correspond to
12974 * the string properties of stateObject
12975 * @private
12976 */
12977 function getFormatManifests (stateObject) {
12978 var manifestAccumulator = {};
12979
12980 NGTweenable.each(stateObject, function (prop) {
12981 var currentProp = stateObject[prop];
12982
12983 if (typeof currentProp === 'string') {
12984 var rawValues = getValuesFrom(currentProp);
12985
12986 manifestAccumulator[prop] = {
12987 'formatString': getFormatStringFrom(currentProp)
12988 ,'chunkNames': getFormatChunksFrom(rawValues, prop)
12989 };
12990 }
12991 });
12992
12993 return manifestAccumulator;
12994 }
12995
12996 /**
12997 * @param {Object} stateObject
12998 * @param {Object} formatManifests
12999 * @private
13000 */
13001 function expandFormattedProperties (stateObject, formatManifests) {
13002 NGTweenable.each(formatManifests, function (prop) {
13003 var currentProp = stateObject[prop];
13004 var rawValues = getValuesFrom(currentProp);
13005 var rawValuesLength = rawValues.length;
13006
13007 for (var i = 0; i < rawValuesLength; i++) {
13008 stateObject[formatManifests[prop].chunkNames[i]] = +rawValues[i];
13009 }
13010
13011 delete stateObject[prop];
13012 });
13013 }
13014
13015 /**
13016 * @param {Object} stateObject
13017 * @param {Object} formatManifests
13018 * @private
13019 */
13020 function collapseFormattedProperties (stateObject, formatManifests) {
13021 NGTweenable.each(formatManifests, function (prop) {
13022 var currentProp = stateObject[prop];
13023 var formatChunks = extractPropertyChunks(
13024 stateObject, formatManifests[prop].chunkNames);
13025 var valuesList = getValuesList(
13026 formatChunks, formatManifests[prop].chunkNames);
13027 currentProp = getFormattedValues(
13028 formatManifests[prop].formatString, valuesList);
13029 stateObject[prop] = sanitizeRGBChunks(currentProp);
13030 });
13031 }
13032
13033 /**
13034 * @param {Object} stateObject
13035 * @param {Array.<string>} chunkNames
13036 *
13037 * @return {Object} The extracted value chunks.
13038 * @private
13039 */
13040 function extractPropertyChunks (stateObject, chunkNames) {
13041 var extractedValues = {};
13042 var currentChunkName, chunkNamesLength = chunkNames.length;
13043
13044 for (var i = 0; i < chunkNamesLength; i++) {
13045 currentChunkName = chunkNames[i];
13046 extractedValues[currentChunkName] = stateObject[currentChunkName];
13047 delete stateObject[currentChunkName];
13048 }
13049
13050 return extractedValues;
13051 }
13052
13053 var getValuesList_accumulator = [];
13054 /**
13055 * @param {Object} stateObject
13056 * @param {Array.<string>} chunkNames
13057 *
13058 * @return {Array.<number>}
13059 * @private
13060 */
13061 function getValuesList (stateObject, chunkNames) {
13062 getValuesList_accumulator.length = 0;
13063 var chunkNamesLength = chunkNames.length;
13064
13065 for (var i = 0; i < chunkNamesLength; i++) {
13066 getValuesList_accumulator.push(stateObject[chunkNames[i]]);
13067 }
13068
13069 return getValuesList_accumulator;
13070 }
13071
13072 /**
13073 * @param {string} formatString
13074 * @param {Array.<number>} rawValues
13075 *
13076 * @return {string}
13077 * @private
13078 */
13079 function getFormattedValues (formatString, rawValues) {
13080 var formattedValueString = formatString;
13081 var rawValuesLength = rawValues.length;
13082
13083 for (var i = 0; i < rawValuesLength; i++) {
13084 formattedValueString = formattedValueString.replace(
13085 VALUE_PLACEHOLDER, +rawValues[i].toFixed(4));
13086 }
13087
13088 return formattedValueString;
13089 }
13090
13091 /**
13092 * Note: It's the duty of the caller to convert the Array elements of the
13093 * return value into numbers. This is a performance optimization.
13094 *
13095 * @param {string} formattedString
13096 *
13097 * @return {Array.<string>|null}
13098 * @private
13099 */
13100 function getValuesFrom (formattedString) {
13101 return formattedString.match(R_UNFORMATTED_VALUES);
13102 }
13103
13104 /**
13105 * @param {Object} easingObject
13106 * @param {Object} tokenData
13107 * @private
13108 */
13109 function expandEasingObject (easingObject, tokenData) {
13110 NGTweenable.each(tokenData, function (prop) {
13111 var currentProp = tokenData[prop];
13112 var chunkNames = currentProp.chunkNames;
13113 var chunkLength = chunkNames.length;
13114
13115 var easing = easingObject[prop];
13116 var i;
13117
13118 if (typeof easing === 'string') {
13119 var easingChunks = easing.split(' ');
13120 var lastEasingChunk = easingChunks[easingChunks.length - 1];
13121
13122 for (i = 0; i < chunkLength; i++) {
13123 easingObject[chunkNames[i]] = easingChunks[i] || lastEasingChunk;
13124 }
13125
13126 } else {
13127 for (i = 0; i < chunkLength; i++) {
13128 easingObject[chunkNames[i]] = easing;
13129 }
13130 }
13131
13132 delete easingObject[prop];
13133 });
13134 }
13135
13136 /**
13137 * @param {Object} easingObject
13138 * @param {Object} tokenData
13139 * @private
13140 */
13141 function collapseEasingObject (easingObject, tokenData) {
13142 NGTweenable.each(tokenData, function (prop) {
13143 var currentProp = tokenData[prop];
13144 var chunkNames = currentProp.chunkNames;
13145 var chunkLength = chunkNames.length;
13146
13147 var firstEasing = easingObject[chunkNames[0]];
13148 var typeofEasings = typeof firstEasing;
13149
13150 if (typeofEasings === 'string') {
13151 var composedEasingString = '';
13152
13153 for (var i = 0; i < chunkLength; i++) {
13154 composedEasingString += ' ' + easingObject[chunkNames[i]];
13155 delete easingObject[chunkNames[i]];
13156 }
13157
13158 easingObject[prop] = composedEasingString.substr(1);
13159 } else {
13160 easingObject[prop] = firstEasing;
13161 }
13162 });
13163 }
13164
13165 NGTweenable.prototype.filter.token = {
13166 'tweenCreated': function (currentState, fromState, toState, easingObject) {
13167 sanitizeObjectForHexProps(currentState);
13168 sanitizeObjectForHexProps(fromState);
13169 sanitizeObjectForHexProps(toState);
13170 this._tokenData = getFormatManifests(currentState);
13171 },
13172
13173 'beforeTween': function (currentState, fromState, toState, easingObject) {
13174 expandEasingObject(easingObject, this._tokenData);
13175 expandFormattedProperties(currentState, this._tokenData);
13176 expandFormattedProperties(fromState, this._tokenData);
13177 expandFormattedProperties(toState, this._tokenData);
13178 },
13179
13180 'afterTween': function (currentState, fromState, toState, easingObject) {
13181 collapseFormattedProperties(currentState, this._tokenData);
13182 collapseFormattedProperties(fromState, this._tokenData);
13183 collapseFormattedProperties(toState, this._tokenData);
13184 collapseEasingObject(easingObject, this._tokenData);
13185 }
13186 };
13187
13188} (NGTweenable));
13189
13190}).call(null);
13191
13192
13193
13194
13195//##########################################################################################################################
13196//## HAMMER.JS #############################################################################################################
13197//##########################################################################################################################
13198
13199// HAMMER.JS
13200
13201// external module EMBEDED in nanogallery
13202// NGY BUILD:
13203// replace "Hammer" with "NGHammer" (case sensitive)
13204// replace "var SUPPORT_POINTER_EVENTS = prefixed(window, 'PointerEvent') !== undefined;" with "var SUPPORT_POINTER_EVENTS = false;"
13205// replace "define.amd" with "define.amdDISABLED"
13206
13207
13208
13209/*! NGHammer.JS - v2.0.7 - 2016-04-22
13210 * http://hammerjs.github.io/
13211 *
13212 * Copyright (c) 2016 Jorik Tangelder;
13213 * Licensed under the MIT license */
13214(function(window, document, exportName, undefined) {
13215 'use strict';
13216
13217var VENDOR_PREFIXES = ['', 'webkit', 'Moz', 'MS', 'ms', 'o'];
13218var TEST_ELEMENT = document.createElement('div');
13219
13220var TYPE_FUNCTION = 'function';
13221
13222var round = Math.round;
13223var abs = Math.abs;
13224var now = Date.now;
13225
13226/**
13227 * set a timeout with a given scope
13228 * @param {Function} fn
13229 * @param {Number} timeout
13230 * @param {Object} context
13231 * @returns {number}
13232 */
13233function setTimeoutContext(fn, timeout, context) {
13234 return setTimeout(bindFn(fn, context), timeout);
13235}
13236
13237/**
13238 * if the argument is an array, we want to execute the fn on each entry
13239 * if it aint an array we don't want to do a thing.
13240 * this is used by all the methods that accept a single and array argument.
13241 * @param {*|Array} arg
13242 * @param {String} fn
13243 * @param {Object} [context]
13244 * @returns {Boolean}
13245 */
13246function invokeArrayArg(arg, fn, context) {
13247 if (Array.isArray(arg)) {
13248 each(arg, context[fn], context);
13249 return true;
13250 }
13251 return false;
13252}
13253
13254/**
13255 * walk objects and arrays
13256 * @param {Object} obj
13257 * @param {Function} iterator
13258 * @param {Object} context
13259 */
13260function each(obj, iterator, context) {
13261 var i;
13262
13263 if (!obj) {
13264 return;
13265 }
13266
13267 if (obj.forEach) {
13268 obj.forEach(iterator, context);
13269 } else if (obj.length !== undefined) {
13270 i = 0;
13271 while (i < obj.length) {
13272 iterator.call(context, obj[i], i, obj);
13273 i++;
13274 }
13275 } else {
13276 for (i in obj) {
13277 obj.hasOwnProperty(i) && iterator.call(context, obj[i], i, obj);
13278 }
13279 }
13280}
13281
13282/**
13283 * wrap a method with a deprecation warning and stack trace
13284 * @param {Function} method
13285 * @param {String} name
13286 * @param {String} message
13287 * @returns {Function} A new function wrapping the supplied method.
13288 */
13289function deprecate(method, name, message) {
13290 var deprecationMessage = 'DEPRECATED METHOD: ' + name + '\n' + message + ' AT \n';
13291 return function() {
13292 var e = new Error('get-stack-trace');
13293 var stack = e && e.stack ? e.stack.replace(/^[^\(]+?[\n$]/gm, '')
13294 .replace(/^\s+at\s+/gm, '')
13295 .replace(/^Object.<anonymous>\s*\(/gm, '{anonymous}()@') : 'Unknown Stack Trace';
13296
13297 var log = window.console && (window.console.warn || window.console.log);
13298 if (log) {
13299 log.call(window.console, deprecationMessage, stack);
13300 }
13301 return method.apply(this, arguments);
13302 };
13303}
13304
13305/**
13306 * extend object.
13307 * means that properties in dest will be overwritten by the ones in src.
13308 * @param {Object} target
13309 * @param {...Object} objects_to_assign
13310 * @returns {Object} target
13311 */
13312var assign;
13313if (typeof Object.assign !== 'function') {
13314 assign = function assign(target) {
13315 if (target === undefined || target === null) {
13316 throw new TypeError('Cannot convert undefined or null to object');
13317 }
13318
13319 var output = Object(target);
13320 for (var index = 1; index < arguments.length; index++) {
13321 var source = arguments[index];
13322 if (source !== undefined && source !== null) {
13323 for (var nextKey in source) {
13324 if (source.hasOwnProperty(nextKey)) {
13325 output[nextKey] = source[nextKey];
13326 }
13327 }
13328 }
13329 }
13330 return output;
13331 };
13332} else {
13333 assign = Object.assign;
13334}
13335
13336/**
13337 * extend object.
13338 * means that properties in dest will be overwritten by the ones in src.
13339 * @param {Object} dest
13340 * @param {Object} src
13341 * @param {Boolean} [merge=false]
13342 * @returns {Object} dest
13343 */
13344var extend = deprecate(function extend(dest, src, merge) {
13345 var keys = Object.keys(src);
13346 var i = 0;
13347 while (i < keys.length) {
13348 if (!merge || (merge && dest[keys[i]] === undefined)) {
13349 dest[keys[i]] = src[keys[i]];
13350 }
13351 i++;
13352 }
13353 return dest;
13354}, 'extend', 'Use `assign`.');
13355
13356/**
13357 * merge the values from src in the dest.
13358 * means that properties that exist in dest will not be overwritten by src
13359 * @param {Object} dest
13360 * @param {Object} src
13361 * @returns {Object} dest
13362 */
13363var merge = deprecate(function merge(dest, src) {
13364 return extend(dest, src, true);
13365}, 'merge', 'Use `assign`.');
13366
13367/**
13368 * simple class inheritance
13369 * @param {Function} child
13370 * @param {Function} base
13371 * @param {Object} [properties]
13372 */
13373function inherit(child, base, properties) {
13374 var baseP = base.prototype,
13375 childP;
13376
13377 childP = child.prototype = Object.create(baseP);
13378 childP.constructor = child;
13379 childP._super = baseP;
13380
13381 if (properties) {
13382 assign(childP, properties);
13383 }
13384}
13385
13386/**
13387 * simple function bind
13388 * @param {Function} fn
13389 * @param {Object} context
13390 * @returns {Function}
13391 */
13392function bindFn(fn, context) {
13393 return function boundFn() {
13394 return fn.apply(context, arguments);
13395 };
13396}
13397
13398/**
13399 * let a boolean value also be a function that must return a boolean
13400 * this first item in args will be used as the context
13401 * @param {Boolean|Function} val
13402 * @param {Array} [args]
13403 * @returns {Boolean}
13404 */
13405function boolOrFn(val, args) {
13406 if (typeof val == TYPE_FUNCTION) {
13407 return val.apply(args ? args[0] || undefined : undefined, args);
13408 }
13409 return val;
13410}
13411
13412/**
13413 * use the val2 when val1 is undefined
13414 * @param {*} val1
13415 * @param {*} val2
13416 * @returns {*}
13417 */
13418function ifUndefined(val1, val2) {
13419 return (val1 === undefined) ? val2 : val1;
13420}
13421
13422/**
13423 * addEventListener with multiple events at once
13424 * @param {EventTarget} target
13425 * @param {String} types
13426 * @param {Function} handler
13427 */
13428function addEventListeners(target, types, handler) {
13429 each(splitStr(types), function(type) {
13430 target.addEventListener(type, handler, false);
13431 });
13432}
13433
13434/**
13435 * removeEventListener with multiple events at once
13436 * @param {EventTarget} target
13437 * @param {String} types
13438 * @param {Function} handler
13439 */
13440function removeEventListeners(target, types, handler) {
13441 each(splitStr(types), function(type) {
13442 target.removeEventListener(type, handler, false);
13443 });
13444}
13445
13446/**
13447 * find if a node is in the given parent
13448 * @method hasParent
13449 * @param {HTMLElement} node
13450 * @param {HTMLElement} parent
13451 * @return {Boolean} found
13452 */
13453function hasParent(node, parent) {
13454 while (node) {
13455 if (node == parent) {
13456 return true;
13457 }
13458 node = node.parentNode;
13459 }
13460 return false;
13461}
13462
13463/**
13464 * small indexOf wrapper
13465 * @param {String} str
13466 * @param {String} find
13467 * @returns {Boolean} found
13468 */
13469function inStr(str, find) {
13470 return str.indexOf(find) > -1;
13471}
13472
13473/**
13474 * split string on whitespace
13475 * @param {String} str
13476 * @returns {Array} words
13477 */
13478function splitStr(str) {
13479 return str.trim().split(/\s+/g);
13480}
13481
13482/**
13483 * find if a array contains the object using indexOf or a simple polyFill
13484 * @param {Array} src
13485 * @param {String} find
13486 * @param {String} [findByKey]
13487 * @return {Boolean|Number} false when not found, or the index
13488 */
13489function inArray(src, find, findByKey) {
13490 if (src.indexOf && !findByKey) {
13491 return src.indexOf(find);
13492 } else {
13493 var i = 0;
13494 while (i < src.length) {
13495 if ((findByKey && src[i][findByKey] == find) || (!findByKey && src[i] === find)) {
13496 return i;
13497 }
13498 i++;
13499 }
13500 return -1;
13501 }
13502}
13503
13504/**
13505 * convert array-like objects to real arrays
13506 * @param {Object} obj
13507 * @returns {Array}
13508 */
13509function toArray(obj) {
13510 return Array.prototype.slice.call(obj, 0);
13511}
13512
13513/**
13514 * unique array with objects based on a key (like 'id') or just by the array's value
13515 * @param {Array} src [{id:1},{id:2},{id:1}]
13516 * @param {String} [key]
13517 * @param {Boolean} [sort=False]
13518 * @returns {Array} [{id:1},{id:2}]
13519 */
13520function uniqueArray(src, key, sort) {
13521 var results = [];
13522 var values = [];
13523 var i = 0;
13524
13525 while (i < src.length) {
13526 var val = key ? src[i][key] : src[i];
13527 if (inArray(values, val) < 0) {
13528 results.push(src[i]);
13529 }
13530 values[i] = val;
13531 i++;
13532 }
13533
13534 if (sort) {
13535 if (!key) {
13536 results = results.sort();
13537 } else {
13538 results = results.sort(function sortUniqueArray(a, b) {
13539 return a[key] > b[key];
13540 });
13541 }
13542 }
13543
13544 return results;
13545}
13546
13547/**
13548 * get the prefixed property
13549 * @param {Object} obj
13550 * @param {String} property
13551 * @returns {String|Undefined} prefixed
13552 */
13553function prefixed(obj, property) {
13554 var prefix, prop;
13555 var camelProp = property[0].toUpperCase() + property.slice(1);
13556
13557 var i = 0;
13558 while (i < VENDOR_PREFIXES.length) {
13559 prefix = VENDOR_PREFIXES[i];
13560 prop = (prefix) ? prefix + camelProp : property;
13561
13562 if (prop in obj) {
13563 return prop;
13564 }
13565 i++;
13566 }
13567 return undefined;
13568}
13569
13570/**
13571 * get a unique id
13572 * @returns {number} uniqueId
13573 */
13574var _uniqueId = 1;
13575function uniqueId() {
13576 return _uniqueId++;
13577}
13578
13579/**
13580 * get the window object of an element
13581 * @param {HTMLElement} element
13582 * @returns {DocumentView|Window}
13583 */
13584function getWindowForElement(element) {
13585 var doc = element.ownerDocument || element;
13586 return (doc.defaultView || doc.parentWindow || window);
13587}
13588
13589var MOBILE_REGEX = /mobile|tablet|ip(ad|hone|od)|android/i;
13590
13591var SUPPORT_TOUCH = ('ontouchstart' in window);
13592// var SUPPORT_POINTER_EVENTS = prefixed(window, 'PointerEvent') !== undefined;
13593var SUPPORT_POINTER_EVENTS = false;
13594var SUPPORT_ONLY_TOUCH = SUPPORT_TOUCH && MOBILE_REGEX.test(navigator.userAgent);
13595
13596var INPUT_TYPE_TOUCH = 'touch';
13597var INPUT_TYPE_PEN = 'pen';
13598var INPUT_TYPE_MOUSE = 'mouse';
13599var INPUT_TYPE_KINECT = 'kinect';
13600
13601var COMPUTE_INTERVAL = 25;
13602
13603var INPUT_START = 1;
13604var INPUT_MOVE = 2;
13605var INPUT_END = 4;
13606var INPUT_CANCEL = 8;
13607
13608var DIRECTION_NONE = 1;
13609var DIRECTION_LEFT = 2;
13610var DIRECTION_RIGHT = 4;
13611var DIRECTION_UP = 8;
13612var DIRECTION_DOWN = 16;
13613
13614var DIRECTION_HORIZONTAL = DIRECTION_LEFT | DIRECTION_RIGHT;
13615var DIRECTION_VERTICAL = DIRECTION_UP | DIRECTION_DOWN;
13616var DIRECTION_ALL = DIRECTION_HORIZONTAL | DIRECTION_VERTICAL;
13617
13618var PROPS_XY = ['x', 'y'];
13619var PROPS_CLIENT_XY = ['clientX', 'clientY'];
13620
13621/**
13622 * create new input type manager
13623 * @param {Manager} manager
13624 * @param {Function} callback
13625 * @returns {Input}
13626 * @constructor
13627 */
13628function Input(manager, callback) {
13629 var self = this;
13630 this.manager = manager;
13631 this.callback = callback;
13632 this.element = manager.element;
13633 this.target = manager.options.inputTarget;
13634
13635 // smaller wrapper around the handler, for the scope and the enabled state of the manager,
13636 // so when disabled the input events are completely bypassed.
13637 this.domHandler = function(ev) {
13638 if (boolOrFn(manager.options.enable, [manager])) {
13639 self.handler(ev);
13640 }
13641 };
13642
13643 this.init();
13644
13645}
13646
13647Input.prototype = {
13648 /**
13649 * should handle the inputEvent data and trigger the callback
13650 * @virtual
13651 */
13652 handler: function() { },
13653
13654 /**
13655 * bind the events
13656 */
13657 init: function() {
13658 this.evEl && addEventListeners(this.element, this.evEl, this.domHandler);
13659 this.evTarget && addEventListeners(this.target, this.evTarget, this.domHandler);
13660 this.evWin && addEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler);
13661 },
13662
13663 /**
13664 * unbind the events
13665 */
13666 destroy: function() {
13667 this.evEl && removeEventListeners(this.element, this.evEl, this.domHandler);
13668 this.evTarget && removeEventListeners(this.target, this.evTarget, this.domHandler);
13669 this.evWin && removeEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler);
13670 }
13671};
13672
13673/**
13674 * create new input type manager
13675 * called by the Manager constructor
13676 * @param {NGHammer} manager
13677 * @returns {Input}
13678 */
13679function createInputInstance(manager) {
13680 var Type;
13681 var inputClass = manager.options.inputClass;
13682
13683 if (inputClass) {
13684 Type = inputClass;
13685 } else if (SUPPORT_POINTER_EVENTS) {
13686 Type = PointerEventInput;
13687 } else if (SUPPORT_ONLY_TOUCH) {
13688 Type = TouchInput;
13689 } else if (!SUPPORT_TOUCH) {
13690 Type = MouseInput;
13691 } else {
13692 Type = TouchMouseInput;
13693 }
13694 return new (Type)(manager, inputHandler);
13695}
13696
13697/**
13698 * handle input events
13699 * @param {Manager} manager
13700 * @param {String} eventType
13701 * @param {Object} input
13702 */
13703function inputHandler(manager, eventType, input) {
13704 var pointersLen = input.pointers.length;
13705 var changedPointersLen = input.changedPointers.length;
13706 var isFirst = (eventType & INPUT_START && (pointersLen - changedPointersLen === 0));
13707 var isFinal = (eventType & (INPUT_END | INPUT_CANCEL) && (pointersLen - changedPointersLen === 0));
13708
13709 input.isFirst = !!isFirst;
13710 input.isFinal = !!isFinal;
13711
13712 if (isFirst) {
13713 manager.session = {};
13714 }
13715
13716 // source event is the normalized value of the domEvents
13717 // like 'touchstart, mouseup, pointerdown'
13718 input.eventType = eventType;
13719
13720 // compute scale, rotation etc
13721 computeInputData(manager, input);
13722
13723 // emit secret event
13724 manager.emit('hammer.input', input);
13725
13726 manager.recognize(input);
13727 manager.session.prevInput = input;
13728}
13729
13730/**
13731 * extend the data with some usable properties like scale, rotate, velocity etc
13732 * @param {Object} manager
13733 * @param {Object} input
13734 */
13735function computeInputData(manager, input) {
13736 var session = manager.session;
13737 var pointers = input.pointers;
13738 var pointersLength = pointers.length;
13739
13740 // store the first input to calculate the distance and direction
13741 if (!session.firstInput) {
13742 session.firstInput = simpleCloneInputData(input);
13743 }
13744
13745 // to compute scale and rotation we need to store the multiple touches
13746 if (pointersLength > 1 && !session.firstMultiple) {
13747 session.firstMultiple = simpleCloneInputData(input);
13748 } else if (pointersLength === 1) {
13749 session.firstMultiple = false;
13750 }
13751
13752 var firstInput = session.firstInput;
13753 var firstMultiple = session.firstMultiple;
13754 var offsetCenter = firstMultiple ? firstMultiple.center : firstInput.center;
13755
13756 var center = input.center = getCenter(pointers);
13757 input.timeStamp = now();
13758 input.deltaTime = input.timeStamp - firstInput.timeStamp;
13759
13760 input.angle = getAngle(offsetCenter, center);
13761 input.distance = getDistance(offsetCenter, center);
13762
13763 computeDeltaXY(session, input);
13764 input.offsetDirection = getDirection(input.deltaX, input.deltaY);
13765
13766 var overallVelocity = getVelocity(input.deltaTime, input.deltaX, input.deltaY);
13767 input.overallVelocityX = overallVelocity.x;
13768 input.overallVelocityY = overallVelocity.y;
13769 input.overallVelocity = (abs(overallVelocity.x) > abs(overallVelocity.y)) ? overallVelocity.x : overallVelocity.y;
13770
13771 input.scale = firstMultiple ? getScale(firstMultiple.pointers, pointers) : 1;
13772 input.rotation = firstMultiple ? getRotation(firstMultiple.pointers, pointers) : 0;
13773
13774 input.maxPointers = !session.prevInput ? input.pointers.length : ((input.pointers.length >
13775 session.prevInput.maxPointers) ? input.pointers.length : session.prevInput.maxPointers);
13776
13777 computeIntervalInputData(session, input);
13778
13779 // find the correct target
13780 var target = manager.element;
13781 if (hasParent(input.srcEvent.target, target)) {
13782 target = input.srcEvent.target;
13783 }
13784 input.target = target;
13785}
13786
13787function computeDeltaXY(session, input) {
13788 var center = input.center;
13789 var offset = session.offsetDelta || {};
13790 var prevDelta = session.prevDelta || {};
13791 var prevInput = session.prevInput || {};
13792
13793 if (input.eventType === INPUT_START || prevInput.eventType === INPUT_END) {
13794 prevDelta = session.prevDelta = {
13795 x: prevInput.deltaX || 0,
13796 y: prevInput.deltaY || 0
13797 };
13798
13799 offset = session.offsetDelta = {
13800 x: center.x,
13801 y: center.y
13802 };
13803 }
13804
13805 input.deltaX = prevDelta.x + (center.x - offset.x);
13806 input.deltaY = prevDelta.y + (center.y - offset.y);
13807}
13808
13809/**
13810 * velocity is calculated every x ms
13811 * @param {Object} session
13812 * @param {Object} input
13813 */
13814function computeIntervalInputData(session, input) {
13815 var last = session.lastInterval || input,
13816 deltaTime = input.timeStamp - last.timeStamp,
13817 velocity, velocityX, velocityY, direction;
13818
13819 if (input.eventType != INPUT_CANCEL && (deltaTime > COMPUTE_INTERVAL || last.velocity === undefined)) {
13820 var deltaX = input.deltaX - last.deltaX;
13821 var deltaY = input.deltaY - last.deltaY;
13822
13823 var v = getVelocity(deltaTime, deltaX, deltaY);
13824 velocityX = v.x;
13825 velocityY = v.y;
13826 velocity = (abs(v.x) > abs(v.y)) ? v.x : v.y;
13827 direction = getDirection(deltaX, deltaY);
13828
13829 session.lastInterval = input;
13830 } else {
13831 // use latest velocity info if it doesn't overtake a minimum period
13832 velocity = last.velocity;
13833 velocityX = last.velocityX;
13834 velocityY = last.velocityY;
13835 direction = last.direction;
13836 }
13837
13838 input.velocity = velocity;
13839 input.velocityX = velocityX;
13840 input.velocityY = velocityY;
13841 input.direction = direction;
13842}
13843
13844/**
13845 * create a simple clone from the input used for storage of firstInput and firstMultiple
13846 * @param {Object} input
13847 * @returns {Object} clonedInputData
13848 */
13849function simpleCloneInputData(input) {
13850 // make a simple copy of the pointers because we will get a reference if we don't
13851 // we only need clientXY for the calculations
13852 var pointers = [];
13853 var i = 0;
13854 while (i < input.pointers.length) {
13855 pointers[i] = {
13856 clientX: round(input.pointers[i].clientX),
13857 clientY: round(input.pointers[i].clientY)
13858 };
13859 i++;
13860 }
13861
13862 return {
13863 timeStamp: now(),
13864 pointers: pointers,
13865 center: getCenter(pointers),
13866 deltaX: input.deltaX,
13867 deltaY: input.deltaY
13868 };
13869}
13870
13871/**
13872 * get the center of all the pointers
13873 * @param {Array} pointers
13874 * @return {Object} center contains `x` and `y` properties
13875 */
13876function getCenter(pointers) {
13877 var pointersLength = pointers.length;
13878
13879 // no need to loop when only one touch
13880 if (pointersLength === 1) {
13881 return {
13882 x: round(pointers[0].clientX),
13883 y: round(pointers[0].clientY)
13884 };
13885 }
13886
13887 var x = 0, y = 0, i = 0;
13888 while (i < pointersLength) {
13889 x += pointers[i].clientX;
13890 y += pointers[i].clientY;
13891 i++;
13892 }
13893
13894 return {
13895 x: round(x / pointersLength),
13896 y: round(y / pointersLength)
13897 };
13898}
13899
13900/**
13901 * calculate the velocity between two points. unit is in px per ms.
13902 * @param {Number} deltaTime
13903 * @param {Number} x
13904 * @param {Number} y
13905 * @return {Object} velocity `x` and `y`
13906 */
13907function getVelocity(deltaTime, x, y) {
13908 return {
13909 x: x / deltaTime || 0,
13910 y: y / deltaTime || 0
13911 };
13912}
13913
13914/**
13915 * get the direction between two points
13916 * @param {Number} x
13917 * @param {Number} y
13918 * @return {Number} direction
13919 */
13920function getDirection(x, y) {
13921 if (x === y) {
13922 return DIRECTION_NONE;
13923 }
13924
13925 if (abs(x) >= abs(y)) {
13926 return x < 0 ? DIRECTION_LEFT : DIRECTION_RIGHT;
13927 }
13928 return y < 0 ? DIRECTION_UP : DIRECTION_DOWN;
13929}
13930
13931/**
13932 * calculate the absolute distance between two points
13933 * @param {Object} p1 {x, y}
13934 * @param {Object} p2 {x, y}
13935 * @param {Array} [props] containing x and y keys
13936 * @return {Number} distance
13937 */
13938function getDistance(p1, p2, props) {
13939 if (!props) {
13940 props = PROPS_XY;
13941 }
13942 var x = p2[props[0]] - p1[props[0]],
13943 y = p2[props[1]] - p1[props[1]];
13944
13945 return Math.sqrt((x * x) + (y * y));
13946}
13947
13948/**
13949 * calculate the angle between two coordinates
13950 * @param {Object} p1
13951 * @param {Object} p2
13952 * @param {Array} [props] containing x and y keys
13953 * @return {Number} angle
13954 */
13955function getAngle(p1, p2, props) {
13956 if (!props) {
13957 props = PROPS_XY;
13958 }
13959 var x = p2[props[0]] - p1[props[0]],
13960 y = p2[props[1]] - p1[props[1]];
13961 return Math.atan2(y, x) * 180 / Math.PI;
13962}
13963
13964/**
13965 * calculate the rotation degrees between two pointersets
13966 * @param {Array} start array of pointers
13967 * @param {Array} end array of pointers
13968 * @return {Number} rotation
13969 */
13970function getRotation(start, end) {
13971 return getAngle(end[1], end[0], PROPS_CLIENT_XY) + getAngle(start[1], start[0], PROPS_CLIENT_XY);
13972}
13973
13974/**
13975 * calculate the scale factor between two pointersets
13976 * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out
13977 * @param {Array} start array of pointers
13978 * @param {Array} end array of pointers
13979 * @return {Number} scale
13980 */
13981function getScale(start, end) {
13982 return getDistance(end[0], end[1], PROPS_CLIENT_XY) / getDistance(start[0], start[1], PROPS_CLIENT_XY);
13983}
13984
13985var MOUSE_INPUT_MAP = {
13986 mousedown: INPUT_START,
13987 mousemove: INPUT_MOVE,
13988 mouseup: INPUT_END
13989};
13990
13991var MOUSE_ELEMENT_EVENTS = 'mousedown';
13992var MOUSE_WINDOW_EVENTS = 'mousemove mouseup';
13993
13994/**
13995 * Mouse events input
13996 * @constructor
13997 * @extends Input
13998 */
13999function MouseInput() {
14000 this.evEl = MOUSE_ELEMENT_EVENTS;
14001 this.evWin = MOUSE_WINDOW_EVENTS;
14002
14003 this.pressed = false; // mousedown state
14004
14005 Input.apply(this, arguments);
14006}
14007
14008inherit(MouseInput, Input, {
14009 /**
14010 * handle mouse events
14011 * @param {Object} ev
14012 */
14013 handler: function MEhandler(ev) {
14014 var eventType = MOUSE_INPUT_MAP[ev.type];
14015
14016 // on start we want to have the left mouse button down
14017 if (eventType & INPUT_START && ev.button === 0) {
14018 this.pressed = true;
14019 }
14020
14021 if (eventType & INPUT_MOVE && ev.which !== 1) {
14022 eventType = INPUT_END;
14023 }
14024
14025 // mouse must be down
14026 if (!this.pressed) {
14027 return;
14028 }
14029
14030 if (eventType & INPUT_END) {
14031 this.pressed = false;
14032 }
14033
14034 this.callback(this.manager, eventType, {
14035 pointers: [ev],
14036 changedPointers: [ev],
14037 pointerType: INPUT_TYPE_MOUSE,
14038 srcEvent: ev
14039 });
14040 }
14041});
14042
14043var POINTER_INPUT_MAP = {
14044 pointerdown: INPUT_START,
14045 pointermove: INPUT_MOVE,
14046 pointerup: INPUT_END,
14047 pointercancel: INPUT_CANCEL,
14048 pointerout: INPUT_CANCEL
14049};
14050
14051// in IE10 the pointer types is defined as an enum
14052var IE10_POINTER_TYPE_ENUM = {
14053 2: INPUT_TYPE_TOUCH,
14054 3: INPUT_TYPE_PEN,
14055 4: INPUT_TYPE_MOUSE,
14056 5: INPUT_TYPE_KINECT // see https://twitter.com/jacobrossi/status/480596438489890816
14057};
14058
14059var POINTER_ELEMENT_EVENTS = 'pointerdown';
14060var POINTER_WINDOW_EVENTS = 'pointermove pointerup pointercancel';
14061
14062// IE10 has prefixed support, and case-sensitive
14063if (window.MSPointerEvent && !window.PointerEvent) {
14064 POINTER_ELEMENT_EVENTS = 'MSPointerDown';
14065 POINTER_WINDOW_EVENTS = 'MSPointerMove MSPointerUp MSPointerCancel';
14066}
14067
14068/**
14069 * Pointer events input
14070 * @constructor
14071 * @extends Input
14072 */
14073function PointerEventInput() {
14074 this.evEl = POINTER_ELEMENT_EVENTS;
14075 this.evWin = POINTER_WINDOW_EVENTS;
14076
14077 Input.apply(this, arguments);
14078
14079 this.store = (this.manager.session.pointerEvents = []);
14080}
14081
14082inherit(PointerEventInput, Input, {
14083 /**
14084 * handle mouse events
14085 * @param {Object} ev
14086 */
14087 handler: function PEhandler(ev) {
14088 var store = this.store;
14089 var removePointer = false;
14090
14091 var eventTypeNormalized = ev.type.toLowerCase().replace('ms', '');
14092 var eventType = POINTER_INPUT_MAP[eventTypeNormalized];
14093 var pointerType = IE10_POINTER_TYPE_ENUM[ev.pointerType] || ev.pointerType;
14094
14095 var isTouch = (pointerType == INPUT_TYPE_TOUCH);
14096
14097 // get index of the event in the store
14098 var storeIndex = inArray(store, ev.pointerId, 'pointerId');
14099
14100 // start and mouse must be down
14101 if (eventType & INPUT_START && (ev.button === 0 || isTouch)) {
14102 if (storeIndex < 0) {
14103 store.push(ev);
14104 storeIndex = store.length - 1;
14105 }
14106 } else if (eventType & (INPUT_END | INPUT_CANCEL)) {
14107 removePointer = true;
14108 }
14109
14110 // it not found, so the pointer hasn't been down (so it's probably a hover)
14111 if (storeIndex < 0) {
14112 return;
14113 }
14114
14115 // update the event in the store
14116 store[storeIndex] = ev;
14117
14118 this.callback(this.manager, eventType, {
14119 pointers: store,
14120 changedPointers: [ev],
14121 pointerType: pointerType,
14122 srcEvent: ev
14123 });
14124
14125 if (removePointer) {
14126 // remove from the store
14127 store.splice(storeIndex, 1);
14128 }
14129 }
14130});
14131
14132var SINGLE_TOUCH_INPUT_MAP = {
14133 touchstart: INPUT_START,
14134 touchmove: INPUT_MOVE,
14135 touchend: INPUT_END,
14136 touchcancel: INPUT_CANCEL
14137};
14138
14139var SINGLE_TOUCH_TARGET_EVENTS = 'touchstart';
14140var SINGLE_TOUCH_WINDOW_EVENTS = 'touchstart touchmove touchend touchcancel';
14141
14142/**
14143 * Touch events input
14144 * @constructor
14145 * @extends Input
14146 */
14147function SingleTouchInput() {
14148 this.evTarget = SINGLE_TOUCH_TARGET_EVENTS;
14149 this.evWin = SINGLE_TOUCH_WINDOW_EVENTS;
14150 this.started = false;
14151
14152 Input.apply(this, arguments);
14153}
14154
14155inherit(SingleTouchInput, Input, {
14156 handler: function TEhandler(ev) {
14157 var type = SINGLE_TOUCH_INPUT_MAP[ev.type];
14158
14159 // should we handle the touch events?
14160 if (type === INPUT_START) {
14161 this.started = true;
14162 }
14163
14164 if (!this.started) {
14165 return;
14166 }
14167
14168 var touches = normalizeSingleTouches.call(this, ev, type);
14169
14170 // when done, reset the started state
14171 if (type & (INPUT_END | INPUT_CANCEL) && touches[0].length - touches[1].length === 0) {
14172 this.started = false;
14173 }
14174
14175 this.callback(this.manager, type, {
14176 pointers: touches[0],
14177 changedPointers: touches[1],
14178 pointerType: INPUT_TYPE_TOUCH,
14179 srcEvent: ev
14180 });
14181 }
14182});
14183
14184/**
14185 * @this {TouchInput}
14186 * @param {Object} ev
14187 * @param {Number} type flag
14188 * @returns {undefined|Array} [all, changed]
14189 */
14190function normalizeSingleTouches(ev, type) {
14191 var all = toArray(ev.touches);
14192 var changed = toArray(ev.changedTouches);
14193
14194 if (type & (INPUT_END | INPUT_CANCEL)) {
14195 all = uniqueArray(all.concat(changed), 'identifier', true);
14196 }
14197
14198 return [all, changed];
14199}
14200
14201var TOUCH_INPUT_MAP = {
14202 touchstart: INPUT_START,
14203 touchmove: INPUT_MOVE,
14204 touchend: INPUT_END,
14205 touchcancel: INPUT_CANCEL
14206};
14207
14208var TOUCH_TARGET_EVENTS = 'touchstart touchmove touchend touchcancel';
14209
14210/**
14211 * Multi-user touch events input
14212 * @constructor
14213 * @extends Input
14214 */
14215function TouchInput() {
14216 this.evTarget = TOUCH_TARGET_EVENTS;
14217 this.targetIds = {};
14218
14219 Input.apply(this, arguments);
14220}
14221
14222inherit(TouchInput, Input, {
14223 handler: function MTEhandler(ev) {
14224 var type = TOUCH_INPUT_MAP[ev.type];
14225 var touches = getTouches.call(this, ev, type);
14226 if (!touches) {
14227 return;
14228 }
14229
14230 this.callback(this.manager, type, {
14231 pointers: touches[0],
14232 changedPointers: touches[1],
14233 pointerType: INPUT_TYPE_TOUCH,
14234 srcEvent: ev
14235 });
14236 }
14237});
14238
14239/**
14240 * @this {TouchInput}
14241 * @param {Object} ev
14242 * @param {Number} type flag
14243 * @returns {undefined|Array} [all, changed]
14244 */
14245function getTouches(ev, type) {
14246 var allTouches = toArray(ev.touches);
14247 var targetIds = this.targetIds;
14248
14249 // when there is only one touch, the process can be simplified
14250 if (type & (INPUT_START | INPUT_MOVE) && allTouches.length === 1) {
14251 targetIds[allTouches[0].identifier] = true;
14252 return [allTouches, allTouches];
14253 }
14254
14255 var i,
14256 targetTouches,
14257 changedTouches = toArray(ev.changedTouches),
14258 changedTargetTouches = [],
14259 target = this.target;
14260
14261 // get target touches from touches
14262 targetTouches = allTouches.filter(function(touch) {
14263 return hasParent(touch.target, target);
14264 });
14265
14266 // collect touches
14267 if (type === INPUT_START) {
14268 i = 0;
14269 while (i < targetTouches.length) {
14270 targetIds[targetTouches[i].identifier] = true;
14271 i++;
14272 }
14273 }
14274
14275 // filter changed touches to only contain touches that exist in the collected target ids
14276 i = 0;
14277 while (i < changedTouches.length) {
14278 if (targetIds[changedTouches[i].identifier]) {
14279 changedTargetTouches.push(changedTouches[i]);
14280 }
14281
14282 // cleanup removed touches
14283 if (type & (INPUT_END | INPUT_CANCEL)) {
14284 delete targetIds[changedTouches[i].identifier];
14285 }
14286 i++;
14287 }
14288
14289 if (!changedTargetTouches.length) {
14290 return;
14291 }
14292
14293 return [
14294 // merge targetTouches with changedTargetTouches so it contains ALL touches, including 'end' and 'cancel'
14295 uniqueArray(targetTouches.concat(changedTargetTouches), 'identifier', true),
14296 changedTargetTouches
14297 ];
14298}
14299
14300/**
14301 * Combined touch and mouse input
14302 *
14303 * Touch has a higher priority then mouse, and while touching no mouse events are allowed.
14304 * This because touch devices also emit mouse events while doing a touch.
14305 *
14306 * @constructor
14307 * @extends Input
14308 */
14309
14310var DEDUP_TIMEOUT = 2500;
14311var DEDUP_DISTANCE = 25;
14312
14313function TouchMouseInput() {
14314 Input.apply(this, arguments);
14315
14316 var handler = bindFn(this.handler, this);
14317 this.touch = new TouchInput(this.manager, handler);
14318 this.mouse = new MouseInput(this.manager, handler);
14319
14320 this.primaryTouch = null;
14321 this.lastTouches = [];
14322}
14323
14324inherit(TouchMouseInput, Input, {
14325 /**
14326 * handle mouse and touch events
14327 * @param {NGHammer} manager
14328 * @param {String} inputEvent
14329 * @param {Object} inputData
14330 */
14331 handler: function TMEhandler(manager, inputEvent, inputData) {
14332 var isTouch = (inputData.pointerType == INPUT_TYPE_TOUCH),
14333 isMouse = (inputData.pointerType == INPUT_TYPE_MOUSE);
14334
14335 if (isMouse && inputData.sourceCapabilities && inputData.sourceCapabilities.firesTouchEvents) {
14336 return;
14337 }
14338
14339 // when we're in a touch event, record touches to de-dupe synthetic mouse event
14340 if (isTouch) {
14341 recordTouches.call(this, inputEvent, inputData);
14342 } else if (isMouse && isSyntheticEvent.call(this, inputData)) {
14343 return;
14344 }
14345
14346 this.callback(manager, inputEvent, inputData);
14347 },
14348
14349 /**
14350 * remove the event listeners
14351 */
14352 destroy: function destroy() {
14353 this.touch.destroy();
14354 this.mouse.destroy();
14355 }
14356});
14357
14358function recordTouches(eventType, eventData) {
14359 if (eventType & INPUT_START) {
14360 this.primaryTouch = eventData.changedPointers[0].identifier;
14361 setLastTouch.call(this, eventData);
14362 } else if (eventType & (INPUT_END | INPUT_CANCEL)) {
14363 setLastTouch.call(this, eventData);
14364 }
14365}
14366
14367function setLastTouch(eventData) {
14368 var touch = eventData.changedPointers[0];
14369
14370 if (touch.identifier === this.primaryTouch) {
14371 var lastTouch = {x: touch.clientX, y: touch.clientY};
14372 this.lastTouches.push(lastTouch);
14373 var lts = this.lastTouches;
14374 var removeLastTouch = function() {
14375 var i = lts.indexOf(lastTouch);
14376 if (i > -1) {
14377 lts.splice(i, 1);
14378 }
14379 };
14380 setTimeout(removeLastTouch, DEDUP_TIMEOUT);
14381 }
14382}
14383
14384function isSyntheticEvent(eventData) {
14385 var x = eventData.srcEvent.clientX, y = eventData.srcEvent.clientY;
14386 for (var i = 0; i < this.lastTouches.length; i++) {
14387 var t = this.lastTouches[i];
14388 var dx = Math.abs(x - t.x), dy = Math.abs(y - t.y);
14389 if (dx <= DEDUP_DISTANCE && dy <= DEDUP_DISTANCE) {
14390 return true;
14391 }
14392 }
14393 return false;
14394}
14395
14396var PREFIXED_TOUCH_ACTION = prefixed(TEST_ELEMENT.style, 'touchAction');
14397var NATIVE_TOUCH_ACTION = PREFIXED_TOUCH_ACTION !== undefined;
14398
14399// magical touchAction value
14400var TOUCH_ACTION_COMPUTE = 'compute';
14401var TOUCH_ACTION_AUTO = 'auto';
14402var TOUCH_ACTION_MANIPULATION = 'manipulation'; // not implemented
14403var TOUCH_ACTION_NONE = 'none';
14404var TOUCH_ACTION_PAN_X = 'pan-x';
14405var TOUCH_ACTION_PAN_Y = 'pan-y';
14406var TOUCH_ACTION_MAP = getTouchActionProps();
14407
14408/**
14409 * Touch Action
14410 * sets the touchAction property or uses the js alternative
14411 * @param {Manager} manager
14412 * @param {String} value
14413 * @constructor
14414 */
14415function TouchAction(manager, value) {
14416 this.manager = manager;
14417 this.set(value);
14418}
14419
14420TouchAction.prototype = {
14421 /**
14422 * set the touchAction value on the element or enable the polyfill
14423 * @param {String} value
14424 */
14425 set: function(value) {
14426 // find out the touch-action by the event handlers
14427 if (value == TOUCH_ACTION_COMPUTE) {
14428 value = this.compute();
14429 }
14430
14431 if (NATIVE_TOUCH_ACTION && this.manager.element.style && TOUCH_ACTION_MAP[value]) {
14432 this.manager.element.style[PREFIXED_TOUCH_ACTION] = value;
14433 }
14434 this.actions = value.toLowerCase().trim();
14435 },
14436
14437 /**
14438 * just re-set the touchAction value
14439 */
14440 update: function() {
14441 this.set(this.manager.options.touchAction);
14442 },
14443
14444 /**
14445 * compute the value for the touchAction property based on the recognizer's settings
14446 * @returns {String} value
14447 */
14448 compute: function() {
14449 var actions = [];
14450 each(this.manager.recognizers, function(recognizer) {
14451 if (boolOrFn(recognizer.options.enable, [recognizer])) {
14452 actions = actions.concat(recognizer.getTouchAction());
14453 }
14454 });
14455 return cleanTouchActions(actions.join(' '));
14456 },
14457
14458 /**
14459 * this method is called on each input cycle and provides the preventing of the browser behavior
14460 * @param {Object} input
14461 */
14462 preventDefaults: function(input) {
14463 var srcEvent = input.srcEvent;
14464 var direction = input.offsetDirection;
14465
14466 // if the touch action did prevented once this session
14467 if (this.manager.session.prevented) {
14468 srcEvent.preventDefault();
14469 return;
14470 }
14471
14472 var actions = this.actions;
14473 var hasNone = inStr(actions, TOUCH_ACTION_NONE) && !TOUCH_ACTION_MAP[TOUCH_ACTION_NONE];
14474 var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_Y];
14475 var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_X];
14476
14477 if (hasNone) {
14478 //do not prevent defaults if this is a tap gesture
14479
14480 var isTapPointer = input.pointers.length === 1;
14481 var isTapMovement = input.distance < 2;
14482 var isTapTouchTime = input.deltaTime < 250;
14483
14484 if (isTapPointer && isTapMovement && isTapTouchTime) {
14485 return;
14486 }
14487 }
14488
14489 if (hasPanX && hasPanY) {
14490 // `pan-x pan-y` means browser handles all scrolling/panning, do not prevent
14491 return;
14492 }
14493
14494 if (hasNone ||
14495 (hasPanY && direction & DIRECTION_HORIZONTAL) ||
14496 (hasPanX && direction & DIRECTION_VERTICAL)) {
14497 return this.preventSrc(srcEvent);
14498 }
14499 },
14500
14501 /**
14502 * call preventDefault to prevent the browser's default behavior (scrolling in most cases)
14503 * @param {Object} srcEvent
14504 */
14505 preventSrc: function(srcEvent) {
14506 this.manager.session.prevented = true;
14507 srcEvent.preventDefault();
14508 }
14509};
14510
14511/**
14512 * when the touchActions are collected they are not a valid value, so we need to clean things up. *
14513 * @param {String} actions
14514 * @returns {*}
14515 */
14516function cleanTouchActions(actions) {
14517 // none
14518 if (inStr(actions, TOUCH_ACTION_NONE)) {
14519 return TOUCH_ACTION_NONE;
14520 }
14521
14522 var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X);
14523 var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y);
14524
14525 // if both pan-x and pan-y are set (different recognizers
14526 // for different directions, e.g. horizontal pan but vertical swipe?)
14527 // we need none (as otherwise with pan-x pan-y combined none of these
14528 // recognizers will work, since the browser would handle all panning
14529 if (hasPanX && hasPanY) {
14530 return TOUCH_ACTION_NONE;
14531 }
14532
14533 // pan-x OR pan-y
14534 if (hasPanX || hasPanY) {
14535 return hasPanX ? TOUCH_ACTION_PAN_X : TOUCH_ACTION_PAN_Y;
14536 }
14537
14538 // manipulation
14539 if (inStr(actions, TOUCH_ACTION_MANIPULATION)) {
14540 return TOUCH_ACTION_MANIPULATION;
14541 }
14542
14543 return TOUCH_ACTION_AUTO;
14544}
14545
14546function getTouchActionProps() {
14547 if (!NATIVE_TOUCH_ACTION) {
14548 return false;
14549 }
14550 var touchMap = {};
14551 var cssSupports = window.CSS && window.CSS.supports;
14552 ['auto', 'manipulation', 'pan-y', 'pan-x', 'pan-x pan-y', 'none'].forEach(function(val) {
14553
14554 // If css.supports is not supported but there is native touch-action assume it supports
14555 // all values. This is the case for IE 10 and 11.
14556 touchMap[val] = cssSupports ? window.CSS.supports('touch-action', val) : true;
14557 });
14558 return touchMap;
14559}
14560
14561/**
14562 * Recognizer flow explained; *
14563 * All recognizers have the initial state of POSSIBLE when a input session starts.
14564 * The definition of a input session is from the first input until the last input, with all it's movement in it. *
14565 * Example session for mouse-input: mousedown -> mousemove -> mouseup
14566 *
14567 * On each recognizing cycle (see Manager.recognize) the .recognize() method is executed
14568 * which determines with state it should be.
14569 *
14570 * If the recognizer has the state FAILED, CANCELLED or RECOGNIZED (equals ENDED), it is reset to
14571 * POSSIBLE to give it another change on the next cycle.
14572 *
14573 * Possible
14574 * |
14575 * +-----+---------------+
14576 * | |
14577 * +-----+-----+ |
14578 * | | |
14579 * Failed Cancelled |
14580 * +-------+------+
14581 * | |
14582 * Recognized Began
14583 * |
14584 * Changed
14585 * |
14586 * Ended/Recognized
14587 */
14588var STATE_POSSIBLE = 1;
14589var STATE_BEGAN = 2;
14590var STATE_CHANGED = 4;
14591var STATE_ENDED = 8;
14592var STATE_RECOGNIZED = STATE_ENDED;
14593var STATE_CANCELLED = 16;
14594var STATE_FAILED = 32;
14595
14596/**
14597 * Recognizer
14598 * Every recognizer needs to extend from this class.
14599 * @constructor
14600 * @param {Object} options
14601 */
14602function Recognizer(options) {
14603 this.options = assign({}, this.defaults, options || {});
14604
14605 this.id = uniqueId();
14606
14607 this.manager = null;
14608
14609 // default is enable true
14610 this.options.enable = ifUndefined(this.options.enable, true);
14611
14612 this.state = STATE_POSSIBLE;
14613
14614 this.simultaneous = {};
14615 this.requireFail = [];
14616}
14617
14618Recognizer.prototype = {
14619 /**
14620 * @virtual
14621 * @type {Object}
14622 */
14623 defaults: {},
14624
14625 /**
14626 * set options
14627 * @param {Object} options
14628 * @return {Recognizer}
14629 */
14630 set: function(options) {
14631 assign(this.options, options);
14632
14633 // also update the touchAction, in case something changed about the directions/enabled state
14634 this.manager && this.manager.touchAction.update();
14635 return this;
14636 },
14637
14638 /**
14639 * recognize simultaneous with an other recognizer.
14640 * @param {Recognizer} otherRecognizer
14641 * @returns {Recognizer} this
14642 */
14643 recognizeWith: function(otherRecognizer) {
14644 if (invokeArrayArg(otherRecognizer, 'recognizeWith', this)) {
14645 return this;
14646 }
14647
14648 var simultaneous = this.simultaneous;
14649 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
14650 if (!simultaneous[otherRecognizer.id]) {
14651 simultaneous[otherRecognizer.id] = otherRecognizer;
14652 otherRecognizer.recognizeWith(this);
14653 }
14654 return this;
14655 },
14656
14657 /**
14658 * drop the simultaneous link. it doesnt remove the link on the other recognizer.
14659 * @param {Recognizer} otherRecognizer
14660 * @returns {Recognizer} this
14661 */
14662 dropRecognizeWith: function(otherRecognizer) {
14663 if (invokeArrayArg(otherRecognizer, 'dropRecognizeWith', this)) {
14664 return this;
14665 }
14666
14667 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
14668 delete this.simultaneous[otherRecognizer.id];
14669 return this;
14670 },
14671
14672 /**
14673 * recognizer can only run when an other is failing
14674 * @param {Recognizer} otherRecognizer
14675 * @returns {Recognizer} this
14676 */
14677 requireFailure: function(otherRecognizer) {
14678 if (invokeArrayArg(otherRecognizer, 'requireFailure', this)) {
14679 return this;
14680 }
14681
14682 var requireFail = this.requireFail;
14683 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
14684 if (inArray(requireFail, otherRecognizer) === -1) {
14685 requireFail.push(otherRecognizer);
14686 otherRecognizer.requireFailure(this);
14687 }
14688 return this;
14689 },
14690
14691 /**
14692 * drop the requireFailure link. it does not remove the link on the other recognizer.
14693 * @param {Recognizer} otherRecognizer
14694 * @returns {Recognizer} this
14695 */
14696 dropRequireFailure: function(otherRecognizer) {
14697 if (invokeArrayArg(otherRecognizer, 'dropRequireFailure', this)) {
14698 return this;
14699 }
14700
14701 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
14702 var index = inArray(this.requireFail, otherRecognizer);
14703 if (index > -1) {
14704 this.requireFail.splice(index, 1);
14705 }
14706 return this;
14707 },
14708
14709 /**
14710 * has require failures boolean
14711 * @returns {boolean}
14712 */
14713 hasRequireFailures: function() {
14714 return this.requireFail.length > 0;
14715 },
14716
14717 /**
14718 * if the recognizer can recognize simultaneous with an other recognizer
14719 * @param {Recognizer} otherRecognizer
14720 * @returns {Boolean}
14721 */
14722 canRecognizeWith: function(otherRecognizer) {
14723 return !!this.simultaneous[otherRecognizer.id];
14724 },
14725
14726 /**
14727 * You should use `tryEmit` instead of `emit` directly to check
14728 * that all the needed recognizers has failed before emitting.
14729 * @param {Object} input
14730 */
14731 emit: function(input) {
14732 var self = this;
14733 var state = this.state;
14734
14735 function emit(event) {
14736 self.manager.emit(event, input);
14737 }
14738
14739 // 'panstart' and 'panmove'
14740 if (state < STATE_ENDED) {
14741 emit(self.options.event + stateStr(state));
14742 }
14743
14744 emit(self.options.event); // simple 'eventName' events
14745
14746 if (input.additionalEvent) { // additional event(panleft, panright, pinchin, pinchout...)
14747 emit(input.additionalEvent);
14748 }
14749
14750 // panend and pancancel
14751 if (state >= STATE_ENDED) {
14752 emit(self.options.event + stateStr(state));
14753 }
14754 },
14755
14756 /**
14757 * Check that all the require failure recognizers has failed,
14758 * if true, it emits a gesture event,
14759 * otherwise, setup the state to FAILED.
14760 * @param {Object} input
14761 */
14762 tryEmit: function(input) {
14763 if (this.canEmit()) {
14764 return this.emit(input);
14765 }
14766 // it's failing anyway
14767 this.state = STATE_FAILED;
14768 },
14769
14770 /**
14771 * can we emit?
14772 * @returns {boolean}
14773 */
14774 canEmit: function() {
14775 var i = 0;
14776 while (i < this.requireFail.length) {
14777 if (!(this.requireFail[i].state & (STATE_FAILED | STATE_POSSIBLE))) {
14778 return false;
14779 }
14780 i++;
14781 }
14782 return true;
14783 },
14784
14785 /**
14786 * update the recognizer
14787 * @param {Object} inputData
14788 */
14789 recognize: function(inputData) {
14790 // make a new copy of the inputData
14791 // so we can change the inputData without messing up the other recognizers
14792 var inputDataClone = assign({}, inputData);
14793
14794 // is is enabled and allow recognizing?
14795 if (!boolOrFn(this.options.enable, [this, inputDataClone])) {
14796 this.reset();
14797 this.state = STATE_FAILED;
14798 return;
14799 }
14800
14801 // reset when we've reached the end
14802 if (this.state & (STATE_RECOGNIZED | STATE_CANCELLED | STATE_FAILED)) {
14803 this.state = STATE_POSSIBLE;
14804 }
14805
14806 this.state = this.process(inputDataClone);
14807
14808 // the recognizer has recognized a gesture
14809 // so trigger an event
14810 if (this.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED | STATE_CANCELLED)) {
14811 this.tryEmit(inputDataClone);
14812 }
14813 },
14814
14815 /**
14816 * return the state of the recognizer
14817 * the actual recognizing happens in this method
14818 * @virtual
14819 * @param {Object} inputData
14820 * @returns {Const} STATE
14821 */
14822 process: function(inputData) { }, // jshint ignore:line
14823
14824 /**
14825 * return the preferred touch-action
14826 * @virtual
14827 * @returns {Array}
14828 */
14829 getTouchAction: function() { },
14830
14831 /**
14832 * called when the gesture isn't allowed to recognize
14833 * like when another is being recognized or it is disabled
14834 * @virtual
14835 */
14836 reset: function() { }
14837};
14838
14839/**
14840 * get a usable string, used as event postfix
14841 * @param {Const} state
14842 * @returns {String} state
14843 */
14844function stateStr(state) {
14845 if (state & STATE_CANCELLED) {
14846 return 'cancel';
14847 } else if (state & STATE_ENDED) {
14848 return 'end';
14849 } else if (state & STATE_CHANGED) {
14850 return 'move';
14851 } else if (state & STATE_BEGAN) {
14852 return 'start';
14853 }
14854 return '';
14855}
14856
14857/**
14858 * direction cons to string
14859 * @param {Const} direction
14860 * @returns {String}
14861 */
14862function directionStr(direction) {
14863 if (direction == DIRECTION_DOWN) {
14864 return 'down';
14865 } else if (direction == DIRECTION_UP) {
14866 return 'up';
14867 } else if (direction == DIRECTION_LEFT) {
14868 return 'left';
14869 } else if (direction == DIRECTION_RIGHT) {
14870 return 'right';
14871 }
14872 return '';
14873}
14874
14875/**
14876 * get a recognizer by name if it is bound to a manager
14877 * @param {Recognizer|String} otherRecognizer
14878 * @param {Recognizer} recognizer
14879 * @returns {Recognizer}
14880 */
14881function getRecognizerByNameIfManager(otherRecognizer, recognizer) {
14882 var manager = recognizer.manager;
14883 if (manager) {
14884 return manager.get(otherRecognizer);
14885 }
14886 return otherRecognizer;
14887}
14888
14889/**
14890 * This recognizer is just used as a base for the simple attribute recognizers.
14891 * @constructor
14892 * @extends Recognizer
14893 */
14894function AttrRecognizer() {
14895 Recognizer.apply(this, arguments);
14896}
14897
14898inherit(AttrRecognizer, Recognizer, {
14899 /**
14900 * @namespace
14901 * @memberof AttrRecognizer
14902 */
14903 defaults: {
14904 /**
14905 * @type {Number}
14906 * @default 1
14907 */
14908 pointers: 1
14909 },
14910
14911 /**
14912 * Used to check if it the recognizer receives valid input, like input.distance > 10.
14913 * @memberof AttrRecognizer
14914 * @param {Object} input
14915 * @returns {Boolean} recognized
14916 */
14917 attrTest: function(input) {
14918 var optionPointers = this.options.pointers;
14919 return optionPointers === 0 || input.pointers.length === optionPointers;
14920 },
14921
14922 /**
14923 * Process the input and return the state for the recognizer
14924 * @memberof AttrRecognizer
14925 * @param {Object} input
14926 * @returns {*} State
14927 */
14928 process: function(input) {
14929 var state = this.state;
14930 var eventType = input.eventType;
14931
14932 var isRecognized = state & (STATE_BEGAN | STATE_CHANGED);
14933 var isValid = this.attrTest(input);
14934
14935 // on cancel input and we've recognized before, return STATE_CANCELLED
14936 if (isRecognized && (eventType & INPUT_CANCEL || !isValid)) {
14937 return state | STATE_CANCELLED;
14938 } else if (isRecognized || isValid) {
14939 if (eventType & INPUT_END) {
14940 return state | STATE_ENDED;
14941 } else if (!(state & STATE_BEGAN)) {
14942 return STATE_BEGAN;
14943 }
14944 return state | STATE_CHANGED;
14945 }
14946 return STATE_FAILED;
14947 }
14948});
14949
14950/**
14951 * Pan
14952 * Recognized when the pointer is down and moved in the allowed direction.
14953 * @constructor
14954 * @extends AttrRecognizer
14955 */
14956function PanRecognizer() {
14957 AttrRecognizer.apply(this, arguments);
14958
14959 this.pX = null;
14960 this.pY = null;
14961}
14962
14963inherit(PanRecognizer, AttrRecognizer, {
14964 /**
14965 * @namespace
14966 * @memberof PanRecognizer
14967 */
14968 defaults: {
14969 event: 'pan',
14970 threshold: 10,
14971 pointers: 1,
14972 direction: DIRECTION_ALL
14973 },
14974
14975 getTouchAction: function() {
14976 var direction = this.options.direction;
14977 var actions = [];
14978 if (direction & DIRECTION_HORIZONTAL) {
14979 actions.push(TOUCH_ACTION_PAN_Y);
14980 }
14981 if (direction & DIRECTION_VERTICAL) {
14982 actions.push(TOUCH_ACTION_PAN_X);
14983 }
14984 return actions;
14985 },
14986
14987 directionTest: function(input) {
14988 var options = this.options;
14989 var hasMoved = true;
14990 var distance = input.distance;
14991 var direction = input.direction;
14992 var x = input.deltaX;
14993 var y = input.deltaY;
14994
14995 // lock to axis?
14996 if (!(direction & options.direction)) {
14997 if (options.direction & DIRECTION_HORIZONTAL) {
14998 direction = (x === 0) ? DIRECTION_NONE : (x < 0) ? DIRECTION_LEFT : DIRECTION_RIGHT;
14999 hasMoved = x != this.pX;
15000 distance = Math.abs(input.deltaX);
15001 } else {
15002 direction = (y === 0) ? DIRECTION_NONE : (y < 0) ? DIRECTION_UP : DIRECTION_DOWN;
15003 hasMoved = y != this.pY;
15004 distance = Math.abs(input.deltaY);
15005 }
15006 }
15007 input.direction = direction;
15008 return hasMoved && distance > options.threshold && direction & options.direction;
15009 },
15010
15011 attrTest: function(input) {
15012 return AttrRecognizer.prototype.attrTest.call(this, input) &&
15013 (this.state & STATE_BEGAN || (!(this.state & STATE_BEGAN) && this.directionTest(input)));
15014 },
15015
15016 emit: function(input) {
15017
15018 this.pX = input.deltaX;
15019 this.pY = input.deltaY;
15020
15021 var direction = directionStr(input.direction);
15022
15023 if (direction) {
15024 input.additionalEvent = this.options.event + direction;
15025 }
15026 this._super.emit.call(this, input);
15027 }
15028});
15029
15030/**
15031 * Pinch
15032 * Recognized when two or more pointers are moving toward (zoom-in) or away from each other (zoom-out).
15033 * @constructor
15034 * @extends AttrRecognizer
15035 */
15036function PinchRecognizer() {
15037 AttrRecognizer.apply(this, arguments);
15038}
15039
15040inherit(PinchRecognizer, AttrRecognizer, {
15041 /**
15042 * @namespace
15043 * @memberof PinchRecognizer
15044 */
15045 defaults: {
15046 event: 'pinch',
15047 threshold: 0,
15048 pointers: 2
15049 },
15050
15051 getTouchAction: function() {
15052 return [TOUCH_ACTION_NONE];
15053 },
15054
15055 attrTest: function(input) {
15056 return this._super.attrTest.call(this, input) &&
15057 (Math.abs(input.scale - 1) > this.options.threshold || this.state & STATE_BEGAN);
15058 },
15059
15060 emit: function(input) {
15061 if (input.scale !== 1) {
15062 var inOut = input.scale < 1 ? 'in' : 'out';
15063 input.additionalEvent = this.options.event + inOut;
15064 }
15065 this._super.emit.call(this, input);
15066 }
15067});
15068
15069/**
15070 * Press
15071 * Recognized when the pointer is down for x ms without any movement.
15072 * @constructor
15073 * @extends Recognizer
15074 */
15075function PressRecognizer() {
15076 Recognizer.apply(this, arguments);
15077
15078 this._timer = null;
15079 this._input = null;
15080}
15081
15082inherit(PressRecognizer, Recognizer, {
15083 /**
15084 * @namespace
15085 * @memberof PressRecognizer
15086 */
15087 defaults: {
15088 event: 'press',
15089 pointers: 1,
15090 time: 251, // minimal time of the pointer to be pressed
15091 threshold: 9 // a minimal movement is ok, but keep it low
15092 },
15093
15094 getTouchAction: function() {
15095 return [TOUCH_ACTION_AUTO];
15096 },
15097
15098 process: function(input) {
15099 var options = this.options;
15100 var validPointers = input.pointers.length === options.pointers;
15101 var validMovement = input.distance < options.threshold;
15102 var validTime = input.deltaTime > options.time;
15103
15104 this._input = input;
15105
15106 // we only allow little movement
15107 // and we've reached an end event, so a tap is possible
15108 if (!validMovement || !validPointers || (input.eventType & (INPUT_END | INPUT_CANCEL) && !validTime)) {
15109 this.reset();
15110 } else if (input.eventType & INPUT_START) {
15111 this.reset();
15112 this._timer = setTimeoutContext(function() {
15113 this.state = STATE_RECOGNIZED;
15114 this.tryEmit();
15115 }, options.time, this);
15116 } else if (input.eventType & INPUT_END) {
15117 return STATE_RECOGNIZED;
15118 }
15119 return STATE_FAILED;
15120 },
15121
15122 reset: function() {
15123 clearTimeout(this._timer);
15124 },
15125
15126 emit: function(input) {
15127 if (this.state !== STATE_RECOGNIZED) {
15128 return;
15129 }
15130
15131 if (input && (input.eventType & INPUT_END)) {
15132 this.manager.emit(this.options.event + 'up', input);
15133 } else {
15134 this._input.timeStamp = now();
15135 this.manager.emit(this.options.event, this._input);
15136 }
15137 }
15138});
15139
15140/**
15141 * Rotate
15142 * Recognized when two or more pointer are moving in a circular motion.
15143 * @constructor
15144 * @extends AttrRecognizer
15145 */
15146function RotateRecognizer() {
15147 AttrRecognizer.apply(this, arguments);
15148}
15149
15150inherit(RotateRecognizer, AttrRecognizer, {
15151 /**
15152 * @namespace
15153 * @memberof RotateRecognizer
15154 */
15155 defaults: {
15156 event: 'rotate',
15157 threshold: 0,
15158 pointers: 2
15159 },
15160
15161 getTouchAction: function() {
15162 return [TOUCH_ACTION_NONE];
15163 },
15164
15165 attrTest: function(input) {
15166 return this._super.attrTest.call(this, input) &&
15167 (Math.abs(input.rotation) > this.options.threshold || this.state & STATE_BEGAN);
15168 }
15169});
15170
15171/**
15172 * Swipe
15173 * Recognized when the pointer is moving fast (velocity), with enough distance in the allowed direction.
15174 * @constructor
15175 * @extends AttrRecognizer
15176 */
15177function SwipeRecognizer() {
15178 AttrRecognizer.apply(this, arguments);
15179}
15180
15181inherit(SwipeRecognizer, AttrRecognizer, {
15182 /**
15183 * @namespace
15184 * @memberof SwipeRecognizer
15185 */
15186 defaults: {
15187 event: 'swipe',
15188 threshold: 10,
15189 velocity: 0.3,
15190 direction: DIRECTION_HORIZONTAL | DIRECTION_VERTICAL,
15191 pointers: 1
15192 },
15193
15194 getTouchAction: function() {
15195 return PanRecognizer.prototype.getTouchAction.call(this);
15196 },
15197
15198 attrTest: function(input) {
15199 var direction = this.options.direction;
15200 var velocity;
15201
15202 if (direction & (DIRECTION_HORIZONTAL | DIRECTION_VERTICAL)) {
15203 velocity = input.overallVelocity;
15204 } else if (direction & DIRECTION_HORIZONTAL) {
15205 velocity = input.overallVelocityX;
15206 } else if (direction & DIRECTION_VERTICAL) {
15207 velocity = input.overallVelocityY;
15208 }
15209
15210 return this._super.attrTest.call(this, input) &&
15211 direction & input.offsetDirection &&
15212 input.distance > this.options.threshold &&
15213 input.maxPointers == this.options.pointers &&
15214 abs(velocity) > this.options.velocity && input.eventType & INPUT_END;
15215 },
15216
15217 emit: function(input) {
15218 var direction = directionStr(input.offsetDirection);
15219 if (direction) {
15220 this.manager.emit(this.options.event + direction, input);
15221 }
15222
15223 this.manager.emit(this.options.event, input);
15224 }
15225});
15226
15227/**
15228 * A tap is ecognized when the pointer is doing a small tap/click. Multiple taps are recognized if they occur
15229 * between the given interval and position. The delay option can be used to recognize multi-taps without firing
15230 * a single tap.
15231 *
15232 * The eventData from the emitted event contains the property `tapCount`, which contains the amount of
15233 * multi-taps being recognized.
15234 * @constructor
15235 * @extends Recognizer
15236 */
15237function TapRecognizer() {
15238 Recognizer.apply(this, arguments);
15239
15240 // previous time and center,
15241 // used for tap counting
15242 this.pTime = false;
15243 this.pCenter = false;
15244
15245 this._timer = null;
15246 this._input = null;
15247 this.count = 0;
15248}
15249
15250inherit(TapRecognizer, Recognizer, {
15251 /**
15252 * @namespace
15253 * @memberof PinchRecognizer
15254 */
15255 defaults: {
15256 event: 'tap',
15257 pointers: 1,
15258 taps: 1,
15259 interval: 300, // max time between the multi-tap taps
15260 time: 250, // max time of the pointer to be down (like finger on the screen)
15261 threshold: 9, // a minimal movement is ok, but keep it low
15262 posThreshold: 10 // a multi-tap can be a bit off the initial position
15263 },
15264
15265 getTouchAction: function() {
15266 return [TOUCH_ACTION_MANIPULATION];
15267 },
15268
15269 process: function(input) {
15270 var options = this.options;
15271
15272 var validPointers = input.pointers.length === options.pointers;
15273 var validMovement = input.distance < options.threshold;
15274 var validTouchTime = input.deltaTime < options.time;
15275
15276 this.reset();
15277
15278 if ((input.eventType & INPUT_START) && (this.count === 0)) {
15279 return this.failTimeout();
15280 }
15281
15282 // we only allow little movement
15283 // and we've reached an end event, so a tap is possible
15284 if (validMovement && validTouchTime && validPointers) {
15285 if (input.eventType != INPUT_END) {
15286 return this.failTimeout();
15287 }
15288
15289 var validInterval = this.pTime ? (input.timeStamp - this.pTime < options.interval) : true;
15290 var validMultiTap = !this.pCenter || getDistance(this.pCenter, input.center) < options.posThreshold;
15291
15292 this.pTime = input.timeStamp;
15293 this.pCenter = input.center;
15294
15295 if (!validMultiTap || !validInterval) {
15296 this.count = 1;
15297 } else {
15298 this.count += 1;
15299 }
15300
15301 this._input = input;
15302
15303 // if tap count matches we have recognized it,
15304 // else it has began recognizing...
15305 var tapCount = this.count % options.taps;
15306 if (tapCount === 0) {
15307 // no failing requirements, immediately trigger the tap event
15308 // or wait as long as the multitap interval to trigger
15309 if (!this.hasRequireFailures()) {
15310 return STATE_RECOGNIZED;
15311 } else {
15312 this._timer = setTimeoutContext(function() {
15313 this.state = STATE_RECOGNIZED;
15314 this.tryEmit();
15315 }, options.interval, this);
15316 return STATE_BEGAN;
15317 }
15318 }
15319 }
15320 return STATE_FAILED;
15321 },
15322
15323 failTimeout: function() {
15324 this._timer = setTimeoutContext(function() {
15325 this.state = STATE_FAILED;
15326 }, this.options.interval, this);
15327 return STATE_FAILED;
15328 },
15329
15330 reset: function() {
15331 clearTimeout(this._timer);
15332 },
15333
15334 emit: function() {
15335 if (this.state == STATE_RECOGNIZED) {
15336 this._input.tapCount = this.count;
15337 this.manager.emit(this.options.event, this._input);
15338 }
15339 }
15340});
15341
15342/**
15343 * Simple way to create a manager with a default set of recognizers.
15344 * @param {HTMLElement} element
15345 * @param {Object} [options]
15346 * @constructor
15347 */
15348function NGHammer(element, options) {
15349 options = options || {};
15350 options.recognizers = ifUndefined(options.recognizers, NGHammer.defaults.preset);
15351 return new Manager(element, options);
15352}
15353
15354/**
15355 * @const {string}
15356 */
15357NGHammer.VERSION = '2.0.7';
15358
15359/**
15360 * default settings
15361 * @namespace
15362 */
15363NGHammer.defaults = {
15364 /**
15365 * set if DOM events are being triggered.
15366 * But this is slower and unused by simple implementations, so disabled by default.
15367 * @type {Boolean}
15368 * @default false
15369 */
15370 domEvents: false,
15371
15372 /**
15373 * The value for the touchAction property/fallback.
15374 * When set to `compute` it will magically set the correct value based on the added recognizers.
15375 * @type {String}
15376 * @default compute
15377 */
15378 touchAction: TOUCH_ACTION_COMPUTE,
15379
15380 /**
15381 * @type {Boolean}
15382 * @default true
15383 */
15384 enable: true,
15385
15386 /**
15387 * EXPERIMENTAL FEATURE -- can be removed/changed
15388 * Change the parent input target element.
15389 * If Null, then it is being set the to main element.
15390 * @type {Null|EventTarget}
15391 * @default null
15392 */
15393 inputTarget: null,
15394
15395 /**
15396 * force an input class
15397 * @type {Null|Function}
15398 * @default null
15399 */
15400 inputClass: null,
15401
15402 /**
15403 * Default recognizer setup when calling `NGHammer()`
15404 * When creating a new Manager these will be skipped.
15405 * @type {Array}
15406 */
15407 preset: [
15408 // RecognizerClass, options, [recognizeWith, ...], [requireFailure, ...]
15409 [RotateRecognizer, {enable: false}],
15410 [PinchRecognizer, {enable: false}, ['rotate']],
15411 [SwipeRecognizer, {direction: DIRECTION_HORIZONTAL}],
15412 [PanRecognizer, {direction: DIRECTION_HORIZONTAL}, ['swipe']],
15413 [TapRecognizer],
15414 [TapRecognizer, {event: 'doubletap', taps: 2}, ['tap']],
15415 [PressRecognizer]
15416 ],
15417
15418 /**
15419 * Some CSS properties can be used to improve the working of NGHammer.
15420 * Add them to this method and they will be set when creating a new Manager.
15421 * @namespace
15422 */
15423 cssProps: {
15424 /**
15425 * Disables text selection to improve the dragging gesture. Mainly for desktop browsers.
15426 * @type {String}
15427 * @default 'none'
15428 */
15429 userSelect: 'none',
15430
15431 /**
15432 * Disable the Windows Phone grippers when pressing an element.
15433 * @type {String}
15434 * @default 'none'
15435 */
15436 touchSelect: 'none',
15437
15438 /**
15439 * Disables the default callout shown when you touch and hold a touch target.
15440 * On iOS, when you touch and hold a touch target such as a link, Safari displays
15441 * a callout containing information about the link. This property allows you to disable that callout.
15442 * @type {String}
15443 * @default 'none'
15444 */
15445 touchCallout: 'none',
15446
15447 /**
15448 * Specifies whether zooming is enabled. Used by IE10>
15449 * @type {String}
15450 * @default 'none'
15451 */
15452 contentZooming: 'none',
15453
15454 /**
15455 * Specifies that an entire element should be draggable instead of its contents. Mainly for desktop browsers.
15456 * @type {String}
15457 * @default 'none'
15458 */
15459 userDrag: 'none',
15460
15461 /**
15462 * Overrides the highlight color shown when the user taps a link or a JavaScript
15463 * clickable element in iOS. This property obeys the alpha value, if specified.
15464 * @type {String}
15465 * @default 'rgba(0,0,0,0)'
15466 */
15467 tapHighlightColor: 'rgba(0,0,0,0)'
15468 }
15469};
15470
15471var STOP = 1;
15472var FORCED_STOP = 2;
15473
15474/**
15475 * Manager
15476 * @param {HTMLElement} element
15477 * @param {Object} [options]
15478 * @constructor
15479 */
15480function Manager(element, options) {
15481 this.options = assign({}, NGHammer.defaults, options || {});
15482
15483 this.options.inputTarget = this.options.inputTarget || element;
15484
15485 this.handlers = {};
15486 this.session = {};
15487 this.recognizers = [];
15488 this.oldCssProps = {};
15489
15490 this.element = element;
15491 this.input = createInputInstance(this);
15492 this.touchAction = new TouchAction(this, this.options.touchAction);
15493
15494 toggleCssProps(this, true);
15495
15496 each(this.options.recognizers, function(item) {
15497 var recognizer = this.add(new (item[0])(item[1]));
15498 item[2] && recognizer.recognizeWith(item[2]);
15499 item[3] && recognizer.requireFailure(item[3]);
15500 }, this);
15501}
15502
15503Manager.prototype = {
15504 /**
15505 * set options
15506 * @param {Object} options
15507 * @returns {Manager}
15508 */
15509 set: function(options) {
15510 assign(this.options, options);
15511
15512 // Options that need a little more setup
15513 if (options.touchAction) {
15514 this.touchAction.update();
15515 }
15516 if (options.inputTarget) {
15517 // Clean up existing event listeners and reinitialize
15518 this.input.destroy();
15519 this.input.target = options.inputTarget;
15520 this.input.init();
15521 }
15522 return this;
15523 },
15524
15525 /**
15526 * stop recognizing for this session.
15527 * This session will be discarded, when a new [input]start event is fired.
15528 * When forced, the recognizer cycle is stopped immediately.
15529 * @param {Boolean} [force]
15530 */
15531 stop: function(force) {
15532 this.session.stopped = force ? FORCED_STOP : STOP;
15533 },
15534
15535 /**
15536 * run the recognizers!
15537 * called by the inputHandler function on every movement of the pointers (touches)
15538 * it walks through all the recognizers and tries to detect the gesture that is being made
15539 * @param {Object} inputData
15540 */
15541 recognize: function(inputData) {
15542 var session = this.session;
15543 if (session.stopped) {
15544 return;
15545 }
15546
15547 // run the touch-action polyfill
15548 this.touchAction.preventDefaults(inputData);
15549
15550 var recognizer;
15551 var recognizers = this.recognizers;
15552
15553 // this holds the recognizer that is being recognized.
15554 // so the recognizer's state needs to be BEGAN, CHANGED, ENDED or RECOGNIZED
15555 // if no recognizer is detecting a thing, it is set to `null`
15556 var curRecognizer = session.curRecognizer;
15557
15558 // reset when the last recognizer is recognized
15559 // or when we're in a new session
15560 if (!curRecognizer || (curRecognizer && curRecognizer.state & STATE_RECOGNIZED)) {
15561 curRecognizer = session.curRecognizer = null;
15562 }
15563
15564 var i = 0;
15565 while (i < recognizers.length) {
15566 recognizer = recognizers[i];
15567
15568 // find out if we are allowed try to recognize the input for this one.
15569 // 1. allow if the session is NOT forced stopped (see the .stop() method)
15570 // 2. allow if we still haven't recognized a gesture in this session, or the this recognizer is the one
15571 // that is being recognized.
15572 // 3. allow if the recognizer is allowed to run simultaneous with the current recognized recognizer.
15573 // this can be setup with the `recognizeWith()` method on the recognizer.
15574 if (session.stopped !== FORCED_STOP && ( // 1
15575 !curRecognizer || recognizer == curRecognizer || // 2
15576 recognizer.canRecognizeWith(curRecognizer))) { // 3
15577 recognizer.recognize(inputData);
15578 } else {
15579 recognizer.reset();
15580 }
15581
15582 // if the recognizer has been recognizing the input as a valid gesture, we want to store this one as the
15583 // current active recognizer. but only if we don't already have an active recognizer
15584 if (!curRecognizer && recognizer.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED)) {
15585 curRecognizer = session.curRecognizer = recognizer;
15586 }
15587 i++;
15588 }
15589 },
15590
15591 /**
15592 * get a recognizer by its event name.
15593 * @param {Recognizer|String} recognizer
15594 * @returns {Recognizer|Null}
15595 */
15596 get: function(recognizer) {
15597 if (recognizer instanceof Recognizer) {
15598 return recognizer;
15599 }
15600
15601 var recognizers = this.recognizers;
15602 for (var i = 0; i < recognizers.length; i++) {
15603 if (recognizers[i].options.event == recognizer) {
15604 return recognizers[i];
15605 }
15606 }
15607 return null;
15608 },
15609
15610 /**
15611 * add a recognizer to the manager
15612 * existing recognizers with the same event name will be removed
15613 * @param {Recognizer} recognizer
15614 * @returns {Recognizer|Manager}
15615 */
15616 add: function(recognizer) {
15617 if (invokeArrayArg(recognizer, 'add', this)) {
15618 return this;
15619 }
15620
15621 // remove existing
15622 var existing = this.get(recognizer.options.event);
15623 if (existing) {
15624 this.remove(existing);
15625 }
15626
15627 this.recognizers.push(recognizer);
15628 recognizer.manager = this;
15629
15630 this.touchAction.update();
15631 return recognizer;
15632 },
15633
15634 /**
15635 * remove a recognizer by name or instance
15636 * @param {Recognizer|String} recognizer
15637 * @returns {Manager}
15638 */
15639 remove: function(recognizer) {
15640 if (invokeArrayArg(recognizer, 'remove', this)) {
15641 return this;
15642 }
15643
15644 recognizer = this.get(recognizer);
15645
15646 // let's make sure this recognizer exists
15647 if (recognizer) {
15648 var recognizers = this.recognizers;
15649 var index = inArray(recognizers, recognizer);
15650
15651 if (index !== -1) {
15652 recognizers.splice(index, 1);
15653 this.touchAction.update();
15654 }
15655 }
15656
15657 return this;
15658 },
15659
15660 /**
15661 * bind event
15662 * @param {String} events
15663 * @param {Function} handler
15664 * @returns {EventEmitter} this
15665 */
15666 on: function(events, handler) {
15667 if (events === undefined) {
15668 return;
15669 }
15670 if (handler === undefined) {
15671 return;
15672 }
15673
15674 var handlers = this.handlers;
15675 each(splitStr(events), function(event) {
15676 handlers[event] = handlers[event] || [];
15677 handlers[event].push(handler);
15678 });
15679 return this;
15680 },
15681
15682 /**
15683 * unbind event, leave emit blank to remove all handlers
15684 * @param {String} events
15685 * @param {Function} [handler]
15686 * @returns {EventEmitter} this
15687 */
15688 off: function(events, handler) {
15689 if (events === undefined) {
15690 return;
15691 }
15692
15693 var handlers = this.handlers;
15694 each(splitStr(events), function(event) {
15695 if (!handler) {
15696 delete handlers[event];
15697 } else {
15698 handlers[event] && handlers[event].splice(inArray(handlers[event], handler), 1);
15699 }
15700 });
15701 return this;
15702 },
15703
15704 /**
15705 * emit event to the listeners
15706 * @param {String} event
15707 * @param {Object} data
15708 */
15709 emit: function(event, data) {
15710 // we also want to trigger dom events
15711 if (this.options.domEvents) {
15712 triggerDomEvent(event, data);
15713 }
15714
15715 // no handlers, so skip it all
15716 var handlers = this.handlers[event] && this.handlers[event].slice();
15717 if (!handlers || !handlers.length) {
15718 return;
15719 }
15720
15721 data.type = event;
15722 data.preventDefault = function() {
15723 data.srcEvent.preventDefault();
15724 };
15725
15726 var i = 0;
15727 while (i < handlers.length) {
15728 handlers[i](data);
15729 i++;
15730 }
15731 },
15732
15733 /**
15734 * destroy the manager and unbinds all events
15735 * it doesn't unbind dom events, that is the user own responsibility
15736 */
15737 destroy: function() {
15738 this.element && toggleCssProps(this, false);
15739
15740 this.handlers = {};
15741 this.session = {};
15742 this.input.destroy();
15743 this.element = null;
15744 }
15745};
15746
15747/**
15748 * add/remove the css properties as defined in manager.options.cssProps
15749 * @param {Manager} manager
15750 * @param {Boolean} add
15751 */
15752function toggleCssProps(manager, add) {
15753 var element = manager.element;
15754 if (!element.style) {
15755 return;
15756 }
15757 var prop;
15758 each(manager.options.cssProps, function(value, name) {
15759 prop = prefixed(element.style, name);
15760 if (add) {
15761 manager.oldCssProps[prop] = element.style[prop];
15762 element.style[prop] = value;
15763 } else {
15764 element.style[prop] = manager.oldCssProps[prop] || '';
15765 }
15766 });
15767 if (!add) {
15768 manager.oldCssProps = {};
15769 }
15770}
15771
15772/**
15773 * trigger dom event
15774 * @param {String} event
15775 * @param {Object} data
15776 */
15777function triggerDomEvent(event, data) {
15778 var gestureEvent = document.createEvent('Event');
15779 gestureEvent.initEvent(event, true, true);
15780 gestureEvent.gesture = data;
15781 data.target.dispatchEvent(gestureEvent);
15782}
15783
15784assign(NGHammer, {
15785 INPUT_START: INPUT_START,
15786 INPUT_MOVE: INPUT_MOVE,
15787 INPUT_END: INPUT_END,
15788 INPUT_CANCEL: INPUT_CANCEL,
15789
15790 STATE_POSSIBLE: STATE_POSSIBLE,
15791 STATE_BEGAN: STATE_BEGAN,
15792 STATE_CHANGED: STATE_CHANGED,
15793 STATE_ENDED: STATE_ENDED,
15794 STATE_RECOGNIZED: STATE_RECOGNIZED,
15795 STATE_CANCELLED: STATE_CANCELLED,
15796 STATE_FAILED: STATE_FAILED,
15797
15798 DIRECTION_NONE: DIRECTION_NONE,
15799 DIRECTION_LEFT: DIRECTION_LEFT,
15800 DIRECTION_RIGHT: DIRECTION_RIGHT,
15801 DIRECTION_UP: DIRECTION_UP,
15802 DIRECTION_DOWN: DIRECTION_DOWN,
15803 DIRECTION_HORIZONTAL: DIRECTION_HORIZONTAL,
15804 DIRECTION_VERTICAL: DIRECTION_VERTICAL,
15805 DIRECTION_ALL: DIRECTION_ALL,
15806
15807 Manager: Manager,
15808 Input: Input,
15809 TouchAction: TouchAction,
15810
15811 TouchInput: TouchInput,
15812 MouseInput: MouseInput,
15813 PointerEventInput: PointerEventInput,
15814 TouchMouseInput: TouchMouseInput,
15815 SingleTouchInput: SingleTouchInput,
15816
15817 Recognizer: Recognizer,
15818 AttrRecognizer: AttrRecognizer,
15819 Tap: TapRecognizer,
15820 Pan: PanRecognizer,
15821 Swipe: SwipeRecognizer,
15822 Pinch: PinchRecognizer,
15823 Rotate: RotateRecognizer,
15824 Press: PressRecognizer,
15825
15826 on: addEventListeners,
15827 off: removeEventListeners,
15828 each: each,
15829 merge: merge,
15830 extend: extend,
15831 assign: assign,
15832 inherit: inherit,
15833 bindFn: bindFn,
15834 prefixed: prefixed
15835});
15836
15837// this prevents errors when NGHammer is loaded in the presence of an AMD
15838// style loader but by script tag, not by the loader.
15839var freeGlobal = (typeof window !== 'undefined' ? window : (typeof self !== 'undefined' ? self : {})); // jshint ignore:line
15840freeGlobal.NGHammer = NGHammer;
15841
15842if (typeof define === 'function' && define.amdDISABLED) {
15843 define(function() {
15844 return NGHammer;
15845 });
15846} else if (typeof module != 'undefined' && module.exports) {
15847 module.exports = NGHammer;
15848} else {
15849 window[exportName] = NGHammer;
15850}
15851
15852})(window, document, 'NGHammer');
15853
15854
15855
15856
15857
15858
15859// END NANOGALLERY2
15860// }( jQuery )));
15861}));
15862
15863
15864//##########################################################################################################################
15865//##########################################################################################################################
15866//##########################################################################################################################
15867//##########################################################################################################################
15868//##########################################################################################################################
15869
15870// nanogallery2 auto start whithout javascript call
15871(function(){
15872 'use strict';
15873
15874 function document_ready(callback){
15875 // in case the document is already rendered
15876 if (document.readyState!='loading') callback();
15877 // modern browsers
15878 else if (document.addEventListener) document.addEventListener('DOMContentLoaded', callback);
15879 // IE <= 8
15880 else document.attachEvent('onreadystatechange', function(){
15881 if (document.readyState=='complete') callback();
15882 });
15883 }
15884
15885 document_ready(function(){
15886
15887 // retrieve GALLERIES
15888 var t=document.querySelectorAll('[data-nanogallery2]');
15889 for( var i=0; i < t.length; i++ ) {
15890 jQuery( t[i] ).nanogallery2( jQuery(t[i]).data('nanogallery2') );
15891 }
15892
15893 // retrieve SINGLE ELEMENTS -> ONLY LIGHTBOX / NO GALLERY
15894 var t = document.querySelectorAll('[data-nanogallery2-lightbox]');
15895 for( var i=0; i < t.length; i++ ) {
15896
15897 // set mouse pointer
15898 t[i].classList.add('NGY2ThumbnailLightbox');
15899
15900 // add click event
15901 t[i].addEventListener('click', function(e) {
15902 // disable link tag if <A> element
15903 e.preventDefault();
15904
15905 // default options for standalone lightbox
15906 var options = {
15907 lightboxStandalone: true,
15908 viewerToolbar: { display: false }
15909 };
15910
15911 // group of images
15912 var g = this.dataset.nanogallery2Lgroup;
15913
15914 // Retrieve the lightbox configuration
15915 // it just need to be defined on one of the elements, which will be displayed in the lightbox
15916 var t = document.querySelectorAll('[data-nanogallery2-lightbox]');
15917 for( var i=0; i < t.length; i++ ) {
15918 if( t[i].dataset.nanogallery2Lgroup == g ) {
15919 if( t[i].dataset.nanogallery2Lightbox !== "" ) {
15920 options = jQuery.extend(true, {}, options, jQuery(t[i]).data('nanogallery2Lightbox'));
15921 break;
15922 }
15923 }
15924 }
15925 jQuery( this ).nanogallery2( options );
15926
15927 });
15928
15929 }
15930 });
15931
15932
15933
15934 // jQuery(document).ready(function () {
15935
15936 // var t=document.querySelectorAll('[data-nanogallery2-portable]');
15937 // if( t.length > 0 ) {
15938 // portable mode
15939 // var link = document.createElement('link');
15940 // link.setAttribute("rel", "stylesheet");
15941 // link.setAttribute("type", "text/css");
15942 // link.onload = function(){
15943 // for( var i=0; i < t.length; i++ ) {
15944 // jQuery(t[i]).nanogallery2(jQuery(t[i]).data('nanogallery2-portable'));
15945 // }
15946 // }
15947 // link.setAttribute("href", '//nano.gallery/css/nanogallery2.css');
15948 // document.getElementsByTagName("head")[0].appendChild(link);
15949 // }
15950 // else {
15951 // standard mode
15952
15953 // GALLERIES
15954 // var t=document.querySelectorAll('[data-nanogallery2]');
15955 // for( var i=0; i < t.length; i++ ) {
15956 // jQuery( t[i] ).nanogallery2( jQuery(t[i]).data('nanogallery2') );
15957 // }
15958
15959
15960 // }
15961
15962 // });
15963}).call(null);
15964