UNPKG

621 kBJavaScriptView Raw
1/* nanogallery2 - v3.0.1 - 2020-06-25 - https://nanogallery2.nanostudio.org */
2/*!
3 * @preserve nanogallery2 v3.0.1 - javascript photo / video gallery and lightbox
4 * Homepage: http://nanogallery2.nanostudio.org
5 * Sources: https://github.com/nanostudio-org/nanogallery2
6 *
7 * License: GPLv3 and commercial licence
8 *
9 * Requirements:
10 * - jQuery (http://www.jquery.com) - version >= 1.7.1
11 *
12 * Embeded components:
13 * - shifty (https://github.com/jeremyckahn/shifty)
14 * - imagesloaded (https://github.com/desandro/imagesloaded)
15 * - hammer.js (http://hammerjs.github.io/)
16 * - screenfull.js (https://github.com/sindresorhus/screenfull.js)
17 * Tools:
18 * - webfont generated with http://fontello.com - mainly based on Font Awesome Copyright (C) 2012 by Dave Gandy (http://fontawesome.io/)
19 * - ICO online converter: https://iconverticons.com/online/
20 */
21
22
23// nanogallery v3.0.1
24/*
25
26- fixed: Flickr connector
27- fixed: lightbox error when thumbnails are disabled
28- fixed: fullscreen mode remains activated when lightbox is closed after having been started in fullscreen mode
29- fixed: requestAnimationFrame not used in some case
30- fixed: does not scroll to top of gallery when an album is opened
31- minor bugfixes
32
33*/
34
35
36// ###########################################
37// ##### nanogallery2 as a JQUERY PLUGIN #####
38// ###########################################
39
40
41// Expose plugin as an AMD module if AMD loader is present:
42(function (factory) {
43 "use strict";
44 if (typeof define === 'function' && define.amd) {
45 // AMD. Register as an anonymous module.
46 // define('nanogallery2', ['jquery'], factory);
47 define(['jquery'], factory);
48 } else if (typeof exports === 'object' && typeof require === 'function') {
49 // Browserify
50 factory(require('jquery'));
51 } else {
52 // Browser globals
53 factory(jQuery);
54 }
55}(function ($) {
56// ;(function ($) {
57 "use strict";
58
59 //##### TOOLS/HELPERS ####
60
61 // Convert color to RGB/RGBA
62 function ColorHelperToRGB( color ) {
63 var obj = document.getElementById('ngyColorHelperToRGB');
64 if (obj === null) {
65 obj = document.createElement('div');
66 obj.id = "ngyColorHelperToRGB";
67 obj.style.cssText = 'display: none; color:' + color + ';';
68 document.body.appendChild(obj);
69 }
70
71 var rgb = getComputedStyle(obj).color;
72
73 // to get HEX value:
74 // var rgb = getComputedStyle(obj).color.match(/\d+/g);
75 // var r = parseInt(rgb[0]).toString(16);
76 // var g = parseInt(rgb[1]).toString(16);
77 // var b = parseInt(rgb[2]).toString(16);
78 // var hex = '#' + r + g + b;
79
80 return rgb;
81 }
82
83
84 // ##### helper for color handling
85 // - normalise RGB/RGBA/HEX format
86 // - lighten/darken color
87 // Inspired by:
88 // https://stackoverflow.com/questions/5560248/programmatically-lighten-or-darken-a-hex-color-or-rgb-and-blend-colors
89 // http://www.pimptrizkit.com/?t=20%20Shades
90 function ShadeBlendConvert (p, from, to) {
91 var rgba='';
92 if( from.toUpperCase().substring(0,5) == 'RGBA(' ) {
93 rgba='a';
94 from='rgb('+from.substring(5);
95 }
96
97 if(typeof(p)!="number"||p<-1||p>1||typeof(from)!="string"||(from[0]!='r'&&from[0]!='#')||(typeof(to)!="string"&&typeof(to)!="undefined"))return null;
98 //if(!this.sbcRip)this.sbcRip=function(d){
99 function sbcRip(d){
100 var l=d.length,RGB=new Object();
101 if(l>9){
102 d=d.split(",");
103 if(d.length<3||d.length>4)return null;
104 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;
105 }else{
106 if(l==8||l==6||l<4)return null;
107 if(l<6)d="#"+d[1]+d[1]+d[2]+d[2]+d[3]+d[3]+(l>4?d[4]+""+d[4]:"");
108 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;
109 }
110 return RGB;
111 }
112 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);
113 if(!f||!t)return null;
114 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])+")");
115 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);
116 }
117
118
119 // ##### clone a javascript object
120 function cloneJSObject( obj ) {
121 if (obj === null || typeof obj !== 'object') {
122 return obj;
123 }
124
125 var temp = obj.constructor(); // give temp the original obj's constructor
126 for (var key in obj) {
127 temp[key] = cloneJSObject(obj[key]);
128 }
129 return temp;
130 }
131
132 // get viewport coordinates and size
133 function getViewport() {
134 var $win = jQuery(window);
135 return {
136 l: $win.scrollLeft(),
137 t: $win.scrollTop(),
138 w: $win.width(),
139 h: $win.height()
140 }
141 }
142
143
144 // Check if element is in viewport
145 // avoid if possible (performance issue)
146 /*
147 function inViewport( $elt, threshold ) {
148 var wp = getViewport(),
149 eltOS = $elt.offset(),
150 th = $elt.outerHeight(true),
151 tw = $elt.outerWidth(true);
152 if( eltOS.top >= (wp.t - threshold)
153 && (eltOS.top + th) <= (wp.t + wp.h + threshold)
154 && eltOS.left >= (wp.l - threshold)
155 && (eltOS.left + tw) <= (wp.l + wp.w + threshold) ) {
156 return true;
157 }
158 else {
159 return false;
160 }
161 }
162 */
163
164
165 // Check if whole element is in ViewPort
166 // avoid if possible (performance issue)
167 function inViewportVert( $elt, threshold ) {
168 var wp = getViewport(),
169 eltOS = $elt.offset(),
170 th = $elt.outerHeight(true);
171 //var tw=$elt.outerWidth(true);
172
173 if( wp.t == 0 && (eltOS.top) <= (wp.t + wp.h ) ) { return true; }
174
175 if( eltOS.top >= wp.t && (eltOS.top + th) <= (wp.t + wp.h - threshold) ) {
176 return true;
177 }
178 else {
179 return false;
180 }
181 }
182 // Check if top of the element is in ViewPort
183 function topInViewportVert( $elt, threshold ) {
184 var wp = getViewport(),
185 eltOS = $elt.offset();
186
187 if( eltOS.top >= wp.t && eltOS.top <= (wp.t + wp.h - threshold) ) {
188 return true;
189 }
190 else {
191 return false;
192 }
193 }
194
195
196 // set z-index to display 2 elements on top of all others
197 // function set2ElementsOnTop( start, elt1, elt2 ) {
198 // var highest_index = 0;
199 // if( start=='' ) { start= '*'; }
200 // jQuery(start).each(function() {
201 // var cur = parseInt(jQuery(this).css('z-index'));
202 // highest_index = cur > highest_index ? cur : highest_index;
203 // });
204 // highest_index++;
205 // jQuery(elt2).css('z-index',highest_index+1);
206 // jQuery(elt1).css('z-index',highest_index);
207 // }
208
209 // set z-index to display element on top of all others
210 function setElementOnTop( start, elt ) {
211 var highest_index = 0;
212 if( start == '' ) { start = '*'; }
213 jQuery(start).each(function() {
214 var cur = parseInt(jQuery(this).css('z-index'));
215 highest_index = cur > highest_index ? cur : highest_index;
216 });
217 highest_index++;
218 jQuery(elt).css('z-index',highest_index);
219 }
220
221 // return the real type of the object
222 var toType = function( obj ) {
223 // by Angus Croll - http://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator/
224 return ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase()
225 };
226
227
228 $.nanogallery2 = function (elt, options) {
229 // To avoid scope issues, use '_this' instead of 'this'
230 // to reference this class from internal events and functions.
231 var _this = this;
232
233 // Access to jQuery and DOM versions of element
234 _this.$e = jQuery(elt);
235 _this.e = elt;
236
237 // Add a reverse reference to the DOM object
238 _this.$e.data('nanogallery2data', _this);
239
240 _this.init = function () {
241
242 // define these global objects only once per HTML page
243 if (typeof window.NGY2Item === 'undefined') {
244
245 window.NGY2Tools = (function () {
246
247 function NGY2Tools() {
248 var nextId = 1; // private static --> all instances
249 }
250
251 // check album name - albumList/blockList/allowList
252 NGY2Tools.FilterAlbumName = function( title, ID ) {
253 var s = title.toUpperCase();
254 if( this.albumList.length > 0 ) {
255 for( var j=0; j < this.albumList.length; j++) {
256 if( s === this.albumList[j].toUpperCase() || ID === this.albumList[j] ) {
257 return true;
258 }
259 }
260 }
261 else {
262 var found = false;
263 if( this.allowList !== null ) {
264 //allowList : authorize only album cointaining one of the specified keyword in the title
265 for( var j = 0; j < this.allowList.length; j++) {
266 if( s.indexOf(this.allowList[j]) !== -1 ) {
267 found = true;
268 }
269 }
270 if( !found ) { return false; }
271 }
272
273
274 if( this.blockList !== null ) {
275 //blockList : ignore album cointaining one of the specified keyword in the title
276 for( var j = 0; j < this.blockList.length; j++) {
277 if( s.indexOf(this.blockList[j]) !== -1 ) {
278 return false;
279 }
280 }
281 }
282 return true;
283 }
284 };
285
286
287 /** @function nanoAlert */
288 /* Display an alert message in a specific element */
289 NGY2Tools.NanoAlert = function(context, msg, verbose) {
290 NGY2Tools.NanoConsoleLog.call(context, msg);
291 if( context.$E.conConsole != null ) {
292 context.$E.conConsole.css({visibility: 'visible', minHeight: '100px'});
293 if( verbose == false ) {
294 context.$E.conConsole.append('<p>' + msg + '</p>');
295 }
296 else {
297 context.$E.conConsole.append('<p>nanogallery2: '+ msg + ' [' + context.baseEltID + ']</p>');
298 }
299 }
300 };
301
302
303 /** @function NanoConsoleLog */
304 /* write message to the browser console */
305 NGY2Tools.NanoConsoleLog = function(context, msg) {
306 if (window.console) { console.log('nanogallery2: ' + msg + ' [' + context.baseEltID + ']'); }
307 // debugger;
308 };
309
310
311 /** @function PreloaderDisplay() */
312 /* Display/hide preloader */
313 NGY2Tools.PreloaderDisplay = function(display) {
314 if( display === true ) {
315 // loading bar at the top of the gallery
316 this.$E.conLoadingB.removeClass('nanoGalleryLBarOff').addClass('nanoGalleryLBar');
317 // spinner over album thumbnail
318 if( this.GOM.albumIdxLoading != undefined && this.GOM.albumIdxLoading != -1 ) {
319 let item = this.I[this.GOM.albumIdxLoading];
320 item.$Elts['.nGY2TnImg'].addClass('nGY2GThumbnailLoaderDisplayed');
321 }
322 }
323 else {
324 // loading bar at the top of the gallery
325 this.$E.conLoadingB.removeClass('nanoGalleryLBar').addClass('nanoGalleryLBarOff');
326 // spinner over album thumbnail
327 if( this.GOM.albumIdxLoading != undefined && this.GOM.albumIdxLoading != -1 ) {
328 let item = this.I[this.GOM.albumIdxLoading];
329 item.$Elts['.nGY2TnImg'].removeClass('nGY2GThumbnailLoaderDisplayed');
330 }
331 }
332 };
333
334 // Scrambles the elements of an array
335 //+ Jonas Raoni Soares Silva
336 //@ http://jsfromhell.com/array/shuffle [v1.0]
337 NGY2Tools.AreaShuffle = function (o) {
338 for (var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
339 return o;
340 };
341
342 /** @function GetImageTitleFromURL() */
343 /* retrieve filemane */
344 NGY2Tools.GetImageTitleFromURL = function( imageURL ) {
345 if( this.O.thumbnailLabel.get('title') == '%filename' ) {
346 return (imageURL.split('/').pop()).replace('_',' ');
347 }
348
349 if( this.O.thumbnailLabel.get('title') == '%filenameNoExt' ) {
350 var s=imageURL.split('/').pop();
351 return (s.split('.').shift()).replace('_',' ');
352 }
353 // return imageURL;
354 return '';
355 };
356
357
358 /** @function AlbumPostProcess() */
359 /* post process one album based on plugin general parameters --> sorting/maxItems*/
360 NGY2Tools.AlbumPostProcess = function(albumID) {
361
362 // this function can probably be optimized....
363
364 var sortOrder = this.gallerySorting[this.GOM.curNavLevel];
365 var maxItems = this.galleryMaxItems[this.GOM.curNavLevel];
366
367 if( sortOrder != '' || maxItems > 0 ) {
368
369 // copy album's items to a new array
370 var currentAlbum = this.I.filter( function( obj ) {
371 return( obj.albumID == albumID && obj.kind != 'albumUp' );
372 });
373
374 // sorting options
375 switch( sortOrder ) {
376 case 'RANDOM':
377 currentAlbum = NGY2Tools.AreaShuffle(currentAlbum);
378 break;
379 case 'REVERSED':
380 currentAlbum = currentAlbum.reverse();
381 break;
382 case 'TITLEASC':
383 currentAlbum.sort(function (a, b) {
384 return( (a.title.toUpperCase() < b.title.toUpperCase()) ? -1 : ((a.title.toUpperCase() > b.title.toUpperCase()) ? 1 : 0) );
385 });
386 break;
387 case 'TITLEDESC':
388 currentAlbum.sort(function (a, b) {
389 return( (a.title.toUpperCase() > b.title.toUpperCase()) ? -1 : ((a.title.toUpperCase() < b.title.toUpperCase()) ? 1 : 0) );
390 });
391 break;
392 }
393
394 // max Items
395 if( maxItems > 0 && currentAlbum.length > maxItems ) {
396 currentAlbum.splice(maxItems - 1, currentAlbum.length-maxItems );
397 }
398
399 // remove the albums's items from the global items array
400 this.I.removeIf( function( obj ) {
401 return( obj.albumID == albumID && obj.kind != 'albumUp' );
402 });
403
404 // add the sorted items back to the album
405 this.I.push.apply(this.I, currentAlbum);
406
407 }
408 };
409
410 return NGY2Tools;
411 })();
412
413 // ====================
414 // ===== NGY2Item =====
415 // ====================
416 window.NGY2Item = (function() {
417 var nextId = 1; // private static --> all instances
418
419 // constructor
420 function NGY2Item( itemID ) {
421 //window.NGY2Item = function( itemID ) {
422 var ID = 0; // private
423
424 // public (this instance only)
425 if( itemID === undefined || itemID === null ) {
426 ID = nextId++;
427 }
428 else {
429 ID = itemID;
430 }
431 this.GetID = function () { return ID; };
432
433 // public
434 this.kind = ''; // 'image', 'album' or 'albumUp'
435 this.mediaKind = 'img'; // 'img', 'iframe'
436 this.mediaMarkup = '';
437 this.G = null; // pointer to global instance
438 this.title = ''; // image title
439 this.description = ''; // image description
440 this.albumID = 0; // ID of the parent album
441 this.src = ''; // full sized image URL
442 this.width = 0; // image width
443 this.height = 0; // image height
444 this.destinationURL = ''; // thumbnail destination URL --> open URL instead of displaying image
445 this.downloadURL = ''; // thumbnail download URL --> specify the image for download button
446 this.author = ''; // image/album author
447 this.left = 0; // store position to animate from old to new
448 this.top = 0;
449 this.width = 0; // store size to avoid setting width/height if not required
450 this.height = 0;
451 this.resizedContentWidth= 0; // store size of content (image) to avoid setting width/height if not required
452 this.resizedContentHeight= 0;
453 this.thumbs = { // URLs and sizes for user defined
454 url: { l1: { xs: '', sm:'', me: '', la: '', xl: '' }, lN: { xs: '', sm: '', me: '', la:'', xl: '' } },
455 width: { l1: { xs: 0, sm: 0, me: 0, la: 0 , xl: 0 }, lN: { xs: 0 , sm: 0, me: 0, la: 0, xl: 0 } },
456 height: { l1: { xs: 0, sm: 0, me: 0, la: 0 , xl: 0 }, lN: { xs: 0, sm: 0, me: 0, la: 0, xl: 0 } }
457 };
458 this.thumbnailImgRevealed = false; // thumbnail image already revealed
459 this.imageDominantColors = null; // base64 GIF
460 this.imageDominantColor = null; // HEX RGB
461 this.featured = false; // featured element
462 this.flickrThumbSizes = {}; // store URLs for all available thumbnail sizes (flickr)
463 this.picasaThumbs = null; // store URLs and sizes
464 this.hovered = false; // is the thumbnail currently hovered?
465 this.hoverInitDone = false;
466 this.contentIsLoaded = false; // album: are items already loaded?
467 this.contentLength = 0; // album: number of items (real number of items in memory)
468 this.numberItems = 0; // album: number of items (value returned by data source)
469 this.mediaNumber = 0; // media number in the album
470 this.mediaCounter = 0; // number of medias in an album
471 this.eltTransform = []; // store the CSS transformations
472 this.eltFilter = []; // store the CSS filters
473 this.eltEffect = []; // store data about hover effects animations
474 this.paginationLastPage = 0; // for albums
475 this.paginationLastWidth = 0; // for albums
476 this.customData = {};
477 this.selected = false;
478 this.imageWidth = 0; // image natural (real) width
479 this.imageHeight = 0; // image natural (real) height
480 this.$elt = null; // pointer to the corresponding DOM element
481 this.$Elts = []; // cached pointers to the thumbnail content -> to avoid jQuery().find()
482 this.tags = []; // list of tags of the current item
483 this.albumTagList = []; // list of all the tags of the items contained in the current album
484 this.albumTagListSel = []; // list of currently selected tags (only for albums)
485 this.exif = { exposure: '', flash: '', focallength: '', fstop: '', iso: '', model: '', time: '', location: ''};
486 this.deleted = false; // item is deleted -> do not display anymore
487 this.rotationAngle = 0; // image display rotation angle
488 }
489
490 // public static
491
492 NGY2Item.Get = function( instance, ID ) {
493 var l = instance.I.length;
494 for( var i = 0; i < l; i++ ) {
495 if( instance.I[i].GetID() == ID ) {
496 return instance.I[i];
497 }
498 }
499 return null;
500 };
501
502 NGY2Item.GetIdx = function( instance, ID ) {
503 var l = instance.I.length;
504 for( var i = 0; i < l; i++ ) {
505 if( instance.I[i].GetID() == ID ) {
506 return i;
507 }
508 }
509 return -1;
510 };
511
512 // create new item (image, album or albumUp)
513 NGY2Item.New = function( instance, title, description, ID, albumID, kind, tags ) {
514 var album = NGY2Item.Get( instance, albumID );
515
516 // title translation
517 if( instance.O.titleTranslationMap !== null ) {
518 let obj = instance.O.titleTranslationMap.find(o => o.title === title);
519 if( obj !== undefined ) {
520 title = obj.replace;
521 }
522 }
523
524
525 if( albumID != -1 && albumID != 0 && title !='image gallery by nanogallery2 [build]' ) {
526 if( instance.O.thumbnailLevelUp && album.getContentLength(false) == 0 && instance.O.album == '' ) {
527 // add navigation thumbnail (album up)
528 let item = new NGY2Item('0');
529 instance.I.push( item );
530 album.contentLength += 1;
531 item.title = 'UP';
532 item.albumID = albumID;
533 item.kind = 'albumUp';
534 item.G = instance;
535
536 jQuery.extend( true, item.thumbs.width, instance.tn.defaultSize.width);
537 jQuery.extend( true, item.thumbs.height, instance.tn.defaultSize.height);
538 }
539 }
540
541 var item = NGY2Item.Get(instance, ID);
542 if( item === null ){
543 // create a new item (otherwise, just update the existing one)
544 item = new NGY2Item(ID);
545 instance.I.push(item);
546 if( albumID != -1 && title !='image gallery by nanogallery2 [build]' ) {
547 album.contentLength+=1;
548 }
549 }
550 item.G = instance;
551
552 item.albumID = albumID;
553 item.kind = kind;
554 if( kind == 'image' ) {
555 album.mediaCounter += 1;
556 item.mediaNumber = album.mediaCounter;
557 }
558
559 // check keyword to find features images/albums
560 var kw = instance.O.thumbnailFeaturedKeyword;
561 if( kw != '' ) {
562 // check if item featured based on a keyword in the title or in the description
563 kw = kw.toUpperCase();
564 var p = title.toUpperCase().indexOf(kw);
565 if( p > -1) {
566 item.featured = true;
567 // remove keyword case unsensitive
568 title = title.substring(0, p) + title.substring(p+kw.length, title.length);
569 }
570 p = description.toUpperCase().indexOf(kw);
571 if( p > -1) {
572 item.featured=true;
573 // remove keyword case unsensitive
574 description=description.substring(0, p) + description.substring(p + kw.length, description.length);
575 }
576 }
577
578 // TAGS
579 // if( instance.galleryFilterTags.Get() != false ) {
580 // if( instance.galleryFilterTags.Get() == true ) {
581 // if( tags != '' && tags != undefined ) {
582 // use set tags
583 // item.setTags(tags.split(' '));
584 // }
585 // }
586 // else {
587 // extract tags starting with # (in title)
588 if( typeof instance.galleryFilterTags.Get() == 'string' ) {
589 switch( instance.galleryFilterTags.Get().toUpperCase() ) {
590 case 'TITLE': {
591 let re = /(?:^|\W)#(\w+)(?!\w)/g, match, matches = [];
592 // let tags = "";
593 while (match = re.exec(title)) {
594 matches.push(match[1].replace(/^\s*|\s*$/, '')); //trim trailing/leading whitespace
595 }
596 item.setTags(matches); //tags;
597 title = title.split('#').join(''); //replaceall
598 break;
599 }
600 case 'DESCRIPTION': {
601 let re = /(?:^|\W)#(\w+)(?!\w)/g, match2, matches2 = [];
602 // let tags = "";
603 while (match2 = re.exec(description)) {
604 matches2.push(match2[1].replace(/^\s*|\s*$/, '')); //trim trailing/leading whitespace
605 }
606 item.setTags(matches2); //tags;
607 description = description.split('#').join(''); //replaceall
608 break;
609 }
610 }
611 }
612 else {
613 if( tags != '' && tags != undefined ) {
614 // use set tags
615 item.setTags(tags.split(' '));
616 }
617 }
618 // }
619 // }
620
621 // set (maybe modified) fields title and description
622 item.title = escapeHtml(instance, title);
623 item.description = escapeHtml(instance, description);
624 return item;
625 };
626
627
628 // removes logically current item
629 NGY2Item.prototype.delete = function( ) {
630 this.deleted = true;
631
632 // update content length of parent album
633 this.G.I[NGY2Item.GetIdx(this.G, this.albumID)].contentLength--;
634 this.G.I[NGY2Item.GetIdx(this.G, this.albumID)].numberItems--;
635
636 // check if in GOM and removes it
637 var nbTn = this.G.GOM.items.length;
638 var ID = this.GetID();
639 var foundIdx = -1;
640 var foundGOMidx = -1;
641 for( var i = 0; i < nbTn ; i++ ) {
642 var curTn = this.G.GOM.items[i];
643 var item=this.G.I[curTn.thumbnailIdx];
644 if( item.GetID() == ID ) {
645 // FOUND
646 if( !curTn.neverDisplayed ) {
647 foundIdx = curTn.thumbnailIdx;
648 foundGOMidx = i;
649 }
650 }
651 else {
652 if( foundIdx != -1 ) {
653 if( !curTn.neverDisplayed ) {
654 // update index value
655 item.$getElt('.nGY2GThumbnail').data('index', i-1);
656 item.$getElt('.nGY2GThumbnailImg').data('index', i-1);
657 }
658 }
659 }
660 }
661 if( foundIdx != -1 ) {
662 // delete item in GOM and delete thumbnail
663 var G = this.G;
664 if( this.selected == true ) {
665 this.selected = false;
666 G.GOM.nbSelected--; // update the global counter
667 }
668 if( G.I[foundIdx].$elt !== null ) {
669 G.I[foundIdx].$elt.remove(); // delete thumbnail DOM object
670 }
671 G.GOM.items.splice(foundGOMidx, 1); // delete in GOM
672 if( G.GOM.lastDisplayedIdx != -1 ) {
673 G.GOM.lastDisplayedIdx -= 1;
674 }
675 }
676
677 // TODO: update medianumber of the other item in the same album
678 }
679
680 NGY2Item.prototype.addToGOM = function( ) {
681 // retrieve index
682 var ID = this.GetID();
683 var l = this.G.I.length;
684 for( var idx = 0; idx < l; idx++ ) {
685 var item = this.G.I[idx];
686 if( item.GetID() == ID ) {
687 var w = item.thumbImg().width;
688 var h = item.thumbImg().height;
689 // set default size if required
690 if( h == 0 ) {
691 h = this.G.tn.defaultSize.getHeight();
692 }
693 if( w == 0 ) {
694 w = this.G.tn.defaultSize.getWidth();
695 }
696 // add to GOM -> will be displayed on next refresh/resize
697 var tn = new this.G.GOM.GTn(idx, w, h);
698 this.G.GOM.items.push(tn);
699 break;
700 }
701 }
702
703 }
704
705
706 // function to avoid XSS issue - Cross Site Scripting
707 // original: https://github.com/janl/mustache.js/blob/master/mustache.js#L55
708 var entityMap = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;', '/': '&#x2F;', '`': '&#x60;', '=': '&#x3D;' };
709 function escapeHtml (instance, string) {
710 if( instance.O.allowHTMLinData == true ) {
711 return string;
712 }
713 else {
714 return String(string).replace(/[&<>"'`=\/]/g, function fromEntityMap (s) {
715 return entityMap[s];
716 });
717 }
718 }
719
720
721 NGY2Item.get_nextId = function () {
722 return nextId;
723 };
724
725 //=== public (shared across instances)
726
727 //--- cached sub elements
728 NGY2Item.prototype.$getElt = function( elt, forceRefresh ) {
729 if( this.$elt == null ) { return null; }
730 if( this.$Elts[elt] !== undefined && !forceRefresh == true ) {
731 return this.$Elts[elt];
732 }
733 else {
734 if( elt == '.nGY2GThumbnail' ) {
735 this.$Elts[elt]=this.$elt;
736 }
737 else {
738 this.$Elts[elt]=this.$elt.find(elt);
739 }
740 return this.$Elts[elt];
741 }
742 };
743
744 // remove one element (in DOM and in cache)
745 NGY2Item.prototype.removeElt = function( elt ) {
746 if( this.$elt == null ) { return; }
747 if( this.$Elts[elt] == undefined) { return; }
748 this.$Elts[elt].remove();
749 var index = this.$Elts.indexOf(elt);
750 this.$Elts.splice(index, 1);
751 };
752
753 //--- returns the album containing the item
754 NGY2Item.prototype.album = function() {
755 return this.G.I[NGY2Item.GetIdx(this.G, this.albumID)];
756 };
757
758 //--- viewer - transition can be disabled per media kind - returns true if current media supports transition (swipe)
759 NGY2Item.prototype.mediaTransition = function( ) {
760 if( this.G.O.viewerTransitionMediaKind.indexOf( this.mediaKind ) > -1 ) {
761 return true;
762 }
763 return false;
764 };
765
766 //--- set one image (url and size)
767 NGY2Item.prototype.imageSet = function( src, w, h ) {
768 this.src = src;
769 this.width = w;
770 this.height = h;
771 };
772
773 //--- set one thumbnail (url and size) - screenSize and level are optional
774 NGY2Item.prototype.thumbSet = function( src, w, h, screenSize, level ) {
775 var lst=['xs','sm','me','la','xl'];
776 if( typeof screenSize === 'undefined' || screenSize == '' || screenSize == null ) {
777 for( var i=0; i< lst.length; i++ ) {
778 if( typeof level === 'undefined' || level == '' ) {
779 this.thumbs.url.l1[lst[i]]=src;
780 this.thumbs.height.l1[lst[i]]=h;
781 this.thumbs.width.l1[lst[i]]=w;
782 this.thumbs.url.lN[lst[i]]=src;
783 this.thumbs.height.lN[lst[i]]=h;
784 this.thumbs.width.lN[lst[i]]=w;
785 }
786 else {
787 this.thumbs.url[level][lst[i]]=src;
788 this.thumbs.height[level][lst[i]]=h;
789 this.thumbs.width[level][lst[i]]=w;
790 }
791 }
792 }
793 else {
794 if( typeof level === 'undefined' || level == '' || level == null ) {
795 this.thumbs.url.l1[screenSize]=src;
796 this.thumbs.height.l1[screenSize]=h;
797 this.thumbs.width.l1[screenSize]=w;
798 this.thumbs.url.lN[screenSize]=src;
799 this.thumbs.height.lN[screenSize]=h;
800 this.thumbs.width.lN[screenSize]=w;
801 }
802 else {
803 this.thumbs.url[level][screenSize]=src;
804 this.thumbs.height[level][screenSize]=h;
805 this.thumbs.width[level][screenSize]=w;
806 }
807 }
808
809 for( var i=0; i< lst.length; i++ ) {
810 this.thumbs.height.l1[lst[i]]=h;
811 }
812 for( var i=0; i< lst.length; i++ ) {
813 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() ) {
814 this.thumbs.height.lN[lst[i]]=h;
815 }
816 }
817 };
818
819 //--- set thumbnail image real height for current level/resolution, and for all others level/resolutions having the same settings
820 NGY2Item.prototype.thumbSetImgHeight = function( h ) {
821 var lst=['xs','sm','me','la','xl'];
822 for( var i=0; i< lst.length; i++ ) {
823 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() ) {
824 this.thumbs.height.l1[lst[i]]=h;
825 }
826 }
827 for( var i=0; i< lst.length; i++ ) {
828 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() ) {
829 this.thumbs.height.lN[lst[i]]=h;
830 }
831 }
832 };
833
834 //--- set thumbnail image real width for current level/resolution, and for all others level/resolutions having the same settings
835 NGY2Item.prototype.thumbSetImgWidth = function( w ) {
836 var lst=['xs','sm','me','la','xl'];
837 for( var i=0; i< lst.length; i++ ) {
838 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() ) {
839 this.thumbs.width.l1[lst[i]]=w;
840 }
841 }
842 for( var i=0; i< lst.length; i++ ) {
843 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() ) {
844 this.thumbs.width.lN[lst[i]]=w;
845 }
846 }
847 };
848
849 //--- Returns Thumbnail image (depending of the screen resolution)
850 NGY2Item.prototype.thumbImg = function () {
851 var tnImg = { src: '', width: 0, height: 0 };
852
853 if( this.title == 'image gallery by nanogallery2 [build]' ) {
854 tnImg.src = this.G.emptyGif;
855 tnImg.url = this.G.emptyGif;
856 return tnImg;
857 }
858 tnImg.src = this.thumbs.url[this.G.GOM.curNavLevel][this.G.GOM.curWidth];
859 tnImg.width = this.thumbs.width[this.G.GOM.curNavLevel][this.G.GOM.curWidth];
860 tnImg.height = this.thumbs.height[this.G.GOM.curNavLevel][this.G.GOM.curWidth];
861 return tnImg;
862 };
863
864 //--- Set tags to items and add these tags to the album
865 NGY2Item.prototype.setTags = function( tags ) {
866 if( tags.length > 0 ) {
867 this.tags = tags;
868 var lstTags = this.album().albumTagList;
869 for( var i = 0; i < tags.length; i++ ) {
870 var tfound = false;
871 for( var j = 0; j < lstTags.length; j++ ) {
872 if( tags[i].toUpperCase() == lstTags[j].toUpperCase() ) {
873 tfound = true;
874 }
875 }
876 if( tfound == false) {
877 this.album().albumTagList.push(tags[i])
878 // this.album().albumTagListSel.push(tags[i])
879 }
880 }
881 }
882 };
883
884 //--- check if 1 of current item's tags is selected (tag filter)
885 NGY2Item.prototype.checkTagFilter = function() {
886 if( this.G.galleryFilterTags.Get() != false && this.album().albumTagList.length > 0 ) {
887 if( this.G.O.thumbnailLevelUp && this.kind == 'albumUp' ) {
888 return true;
889 }
890 var found = false;
891 var lstTags = this.album().albumTagListSel;
892 if( lstTags.length == 0 ) {
893 // no tag is selected -> display all items
894 return true;
895 }
896 for( var i = 0; i < this.tags.length; i++ ) {
897 for( var j = 0; j < lstTags.length; j++ ) {
898 if( this.tags[i].toUpperCase() == lstTags[j].toUpperCase() ) {
899 found = true;
900 break;
901 }
902 }
903 }
904 return found;
905 }
906 else
907 return true;
908 };
909
910 //--- check if 1 of current item's tags is found using API search
911 NGY2Item.prototype.isSearchTagFound = function() {
912 if( this.G.GOM.albumSearchTags == '' ) { return true; }
913 if( this.G.O.thumbnailLevelUp && this.kind == 'albumUp' ) { return true; }
914
915 //var lstTags=this.album().albumTagListSel;
916 for( var i = 0; i < this.tags.length; i++ ) {
917 if( this.tags[i].toUpperCase().indexOf( this.G.GOM.albumSearchTags ) >= 0 ) {
918 return true;
919 }
920 }
921 return false;
922 };
923
924 //--- set the URL of the media to display in the viewer
925 //--- markup is defined for images
926 NGY2Item.prototype.setMediaURL = function( url, mediaKind ) {
927 this.src = url;
928 this.mediaKind = mediaKind;
929 if( mediaKind == 'img' ) {
930 this.mediaMarkup = '<img class="nGY2ViewerMedia" src="' + url + '" alt=" " itemprop="contentURL" draggable="false">';
931 }
932 };
933
934
935 //--- check if current item should be displayed
936 NGY2Item.prototype.isToDisplay = function( albumID ) {
937 return this.albumID == albumID && this.checkTagFilter() && this.isSearchFound() && this.isSearchTagFound() && this.deleted == false;
938 };
939
940
941
942 //--- returns the number of items of the current album
943 //--- count using tags filter
944 NGY2Item.prototype.getContentLength = function( filterTags ) {
945 if( filterTags == false || this.albumTagList.length == 0 || this.G.galleryFilterTags.Get() == false ) {
946 return this.contentLength;
947 }
948 else {
949 var l = this.G.I.length;
950 var cnt = 0;
951 var albumID = this.GetID();
952 for( var idx = 0; idx < l; idx++ ) {
953 var item = this.G.I[idx];
954 if( item.isToDisplay(albumID) ) {
955 cnt++;
956 }
957 }
958 return cnt;
959 }
960 };
961
962 NGY2Item.prototype.isSearchFound = function() {
963 if( this.G.GOM.albumSearch != '' ) {
964 if( this.title.toUpperCase().indexOf( this.G.GOM.albumSearch ) == -1 ) {
965 return false;
966 }
967 }
968 return true;
969 }
970
971
972 //--- for future use...
973 NGY2Item.prototype.responsiveURL = function () {
974 var url = '';
975 switch(this.G.O.kind) {
976 case '':
977 url = this.src;
978 break;
979 case 'flickr':
980 url = this.src;
981 break;
982 case 'picasa':
983 case 'google':
984 case 'google2':
985 default:
986 url = this.src;
987 break;
988 }
989 return url;
990 };
991
992
993 //--- Reveal the thumbnail image with animation on opacity
994 NGY2Item.prototype.ThumbnailImageReveal = function () {
995
996 if( this.thumbnailImgRevealed == false ) {
997 this.thumbnailImgRevealed = true;
998 new NGTweenable().tween({
999 from: { opacity: 0 },
1000 to: { opacity: 1 },
1001 attachment: { item: this },
1002 delay: 30,
1003 duration: 400,
1004 easing: 'easeOutQuart',
1005 step: function (state, att) {
1006 var $e=att.item.$getElt('.nGY2TnImg');
1007 if( $e != null ) {
1008 $e.css( state );
1009 }
1010 }
1011 });
1012 }
1013 };
1014
1015
1016 // In case of thumbnails with stacks - apply a percent to a value which include a unit
1017 function ValueApplyPercent( str, percent ) {
1018 str=String(str);
1019 if( str === '0' || percent == 1 ) { return str; }
1020 var n = Number(str.replace(/[a-zA-Z]/g, ''));
1021 var ar = str.match(/([^\-0-9\.]+)/g);
1022 var a = '';
1023 if( ar != null && ar.length > 0 ) {
1024 a = ar.join();
1025 }
1026
1027 if( isNaN(n) || n == 0 ) {
1028 return str;
1029 }
1030
1031 n = n * percent;
1032 return n + a;
1033 }
1034
1035 //--- 2D/3D CSS transform - apply the cached value to element
1036 NGY2Item.prototype.CSSTransformApply = function ( eltClass ) {
1037 var obj = this.eltTransform[eltClass];
1038
1039 if( eltClass == '.nGY2GThumbnail' ) {
1040 // thumbnail
1041 var nbStacks = obj.$elt.length-1;
1042 var pTranslateX = 1;
1043 var pTranslateY = 1;
1044 var pTranslateZ = 1;
1045 var pTranslate = 1;
1046 var pRotateX = 1;
1047 var pRotateY = 1;
1048 var pRotateZ = 1;
1049 var pRotate = 1;
1050 var pScale = 1;
1051 for( var n = nbStacks; n >= 0; n-- ) {
1052 // units must be given with
1053 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) + ')';
1054 if( !(this.G.IE <= 9) && !this.G.isGingerbread ) {
1055 v += ' rotateX(' + ValueApplyPercent(obj.rotateX,pRotateX) + ') rotateY(' + ValueApplyPercent(obj.rotateY,pRotateY) + ') rotateZ(' + ValueApplyPercent(obj.rotateZ,pRotateZ) + ') rotate(' + ValueApplyPercent(obj.rotate,pRotate) + ')';
1056 }
1057 else {
1058 v += ' rotate(' + ValueApplyPercent(obj.rotateZ,pRotateZ) + ')';
1059 }
1060 obj.$elt[n].style[this.G.CSStransformName] = v;
1061
1062 if( nbStacks > 0 ) {
1063 // apply a percent to the stack elements
1064 pTranslateX -= this.G.tn.opt.Get('stacksTranslateX');
1065 pTranslateY -= this.G.tn.opt.Get('stacksTranslateY');
1066 pTranslateZ -= this.G.tn.opt.Get('stacksTranslateZ');
1067 pRotateX -= this.G.tn.opt.Get('stacksRotateX');
1068 pRotateY -= this.G.tn.opt.Get('stacksRotateY');
1069 pRotateZ -= this.G.tn.opt.Get('stacksRotateZ');
1070 pScale -= this.G.tn.opt.Get('stacksScale');
1071 }
1072 }
1073 }
1074 else {
1075 // thumbnail sub element
1076 if( obj.$elt != null ) {
1077 for( var n = 0; n < obj.$elt.length; n++ ) {
1078 if( obj.$elt[n] != undefined ) {
1079 // units must be given with
1080 var v = 'translateX(' + obj.translateX + ') translateY(' + obj.translateY + ') translateZ(' + obj.translateZ + ') scale(' + obj.scale + ') translate(' + obj.translate + ')';
1081 if( !(this.G.IE <= 9) && !this.G.isGingerbread ) {
1082 v += ' rotateX(' + obj.rotateX + ') rotateY(' + obj.rotateY + ') rotateZ(' + obj.rotateZ + ') rotate(' + obj.rotate + ')';
1083 }
1084 else {
1085 v += ' rotate(' + obj.rotateZ + ')';
1086 }
1087 obj.$elt[n].style[this.G.CSStransformName] = v;
1088 }
1089 }
1090 }
1091 }
1092 };
1093
1094 //--- 2D/3D CSS transform - set a value in cache
1095 NGY2Item.prototype.CSSTransformSet = function ( eltClass, transform, value, forceRefresh ) {
1096 if( this.eltTransform[eltClass] == undefined ) {
1097 this.eltTransform[eltClass] = { translateX: 0, translateY: 0, translateZ: 0, rotateX: 0, rotateY: 0, rotateZ: 0, scale: 1, translate: '0px,0px', rotate: 0 };
1098 this.eltTransform[eltClass].$elt = this.$getElt(eltClass);
1099 }
1100 this.eltTransform[eltClass][transform] = value;
1101 if( forceRefresh === true ) {
1102 this.eltTransform[eltClass].$elt = this.$getElt(eltClass, true);
1103 }
1104 };
1105
1106 //--- CSS Filters - apply the cached value to element
1107 NGY2Item.prototype.CSSFilterApply = function ( eltClass ) {
1108 var obj = this.eltFilter[eltClass];
1109 var v = 'blur(' + obj.blur + ') brightness(' + obj.brightness + ') grayscale(' + obj.grayscale + ') sepia(' + obj.sepia + ') contrast(' + obj.contrast + ') opacity(' + obj.opacity + ') saturate(' + obj.saturate + ')';
1110 if( obj.$elt != null ) {
1111 for( var n = 0; n < obj.$elt.length; n++ ) {
1112 if( obj.$elt[n] != undefined ) {
1113 obj.$elt[n].style.WebkitFilter = v;
1114 obj.$elt[n].style.filter = v;
1115 }
1116 }
1117 }
1118 };
1119
1120 //--- CSS Filters - set a value in cache
1121 NGY2Item.prototype.CSSFilterSet = function ( eltClass, filter, value, forceRefresh ) {
1122 if( this.eltFilter[eltClass] == undefined ) {
1123 this.eltFilter[eltClass] = { blur: 0, brightness: '100%', grayscale: '0%', sepia: '0%', contrast: '100%', opacity: '100%', saturate: '100%' };
1124 this.eltFilter[eltClass].$elt = this.$getElt(eltClass);
1125 }
1126 this.eltFilter[eltClass][filter] = value;
1127 if( forceRefresh === true ) {
1128 this.eltTransform[eltClass].$elt = this.$getElt(eltClass, true);
1129 }
1130 };
1131
1132 //--- thumbnail hover animation
1133 NGY2Item.prototype.animate = function ( effect, delay, hoverIn ) {
1134 if( this.$getElt() == null ) { return; }
1135
1136 var context = {};
1137 context.G = this.G;
1138 context.item = this;
1139 context.effect = effect;
1140 context.hoverIn = hoverIn;
1141 context.cssKind = '';
1142 if( hoverIn ) {
1143 // HOVER IN
1144
1145 if( this.eltEffect[effect.element] == undefined ) {
1146 this.eltEffect[effect.element] = [];
1147 }
1148 if( this.eltEffect[effect.element][effect.type] == undefined ) {
1149 this.eltEffect[effect.element][effect.type] = { initialValue: 0, lastValue: 0 };
1150 }
1151 if( effect.firstKeyframe ) {
1152 // store initial and current value -> for use in the back animation
1153 this.eltEffect[effect.element][effect.type] = { initialValue: effect.from, lastValue: effect.from};
1154 }
1155
1156 context.animeFrom = effect.from;
1157 context.animeTo = effect.to;
1158 context.animeDuration = parseInt(effect.duration);
1159 context.animeDelay = 30 + parseInt(effect.delay + delay); // 30ms is a default delay to avoid conflict with other initializations
1160 context.animeEasing = effect.easing;
1161 }
1162 else {
1163 // HOVER OUT
1164 // if( effect.firstKeyframe ) {
1165 context.animeFrom = this.eltEffect[effect.element][effect.type].lastValue;
1166 context.animeTo = this.eltEffect[effect.element][effect.type].initialValue;
1167 // context.animeTo=effect.from;
1168 // }
1169 // else {
1170 // // context.animeFrom=effect.from;
1171 // context.animeFrom = this.eltEffect[effect.element][effect.type].lastValue;
1172 // context.animeTo = this.eltEffect[effect.element][effect.type].initialValue;
1173 // //context.animeTo=effect.to;
1174
1175 // }
1176
1177 context.animeDuration = parseInt(effect.durationBack);
1178 context.animeDelay = 30 + parseInt(effect.delayBack + delay); // 30ms is a default delay to avoid conflict with other initializations
1179 context.animeEasing = effect.easingBack;
1180 }
1181
1182
1183 // detect if animation on CSS transform
1184 var transform=['translateX', 'translateY', 'translateZ', 'scale', 'rotateX', 'rotateY', 'rotateZ'];
1185 for( var i = 0; i < transform.length; i++ ) {
1186 if( effect.type == transform[i] ) {
1187 context.cssKind = 'transform';
1188 break;
1189 }
1190 }
1191
1192 // detect if animation on CSS filter
1193 var filter=['blur', 'brightness', 'grayscale', 'sepia', 'contrast', 'opacity', 'saturate'];
1194 for( var i = 0; i < filter.length; i++ ) {
1195 if( effect.type == filter[i] ) {
1196 context.cssKind = 'filter';
1197 break;
1198 }
1199 }
1200 // handle some special cases
1201 if( hoverIn && effect.element == '.nGY2GThumbnail' && ( effect.type == 'scale' || effect.type == 'rotateX') ) {
1202 this.G.GOM.lastZIndex++;
1203 this.$getElt(effect.element).css('z-index', this.G.GOM.lastZIndex);
1204 // setElementOnTop(this.G.$E.base, this.$getElt(effect.element) );
1205 }
1206
1207 // animation
1208 var tweenable = new NGTweenable();
1209 context.tweenable=tweenable;
1210 tweenable.tween({
1211 attachment: context,
1212 from: { 'v': context.animeFrom },
1213 to: { 'v': context.animeTo },
1214 duration: context.animeDuration, //parseInt(effect.duration),
1215 delay: context.animeDelay, //parseInt(effect.delay),
1216 easing: context.animeEasing, //'easeOutQuart',
1217
1218 step: function (state, att) {
1219 if( att.item.$getElt() == null ) {
1220 // the thumbnail may be destroyed since the start of the animation
1221 att.tweenable.stop(false);
1222 // att.tweenable.dispose();
1223 return;
1224 }
1225 if( att.hoverIn && !att.item.hovered ) {
1226 // thumbnail no more hovered
1227 att.tweenable.stop(false);
1228 // att.tweenable.dispose();
1229 return;
1230 }
1231
1232 if( att.G.VOM.viewerDisplayed ) {
1233 att.tweenable.stop(false);
1234 // att.tweenable.dispose();
1235 return;
1236 }
1237
1238 // test if in delay phase
1239 if( state.v == att.animeFrom ) { return; }
1240
1241 switch( att.cssKind ) {
1242 case 'transform':
1243 // window.ng_draf( function() {
1244 att.item.CSSTransformSet(att.effect.element, att.effect.type, state.v);
1245 att.item.CSSTransformApply( att.effect.element );
1246 // });
1247 break;
1248 case 'filter':
1249 // window.ng_draf( function() {
1250 att.item.CSSFilterSet(att.effect.element, att.effect.type, state.v);
1251 att.item.CSSFilterApply( att.effect.element );
1252 // });
1253 break;
1254 default:
1255 var v=state.v;
1256 if( state.v.substring(0,4) == 'rgb(' || state.v.substring(0,5) == 'rgba(' ) {
1257 // to remove values after the dot (not supported by RGB/RGBA)
1258 // v=ngtinycolor(state.v).toRgbString();
1259 v = ShadeBlendConvert(0, v);
1260 }
1261 // window.ng_draf( function() {
1262 att.item.$getElt( att.effect.element ).css( att.effect.type, v );
1263 // });
1264 break;
1265 }
1266 if( hoverIn ) {
1267 // store value for back animation
1268 att.item.eltEffect[att.effect.element][att.effect.type].lastValue = state.v;
1269 }
1270 },
1271
1272 finish: function (state, att) {
1273 if( hoverIn ) {
1274 // store value for back animation
1275 att.item.eltEffect[att.effect.element][att.effect.type].lastValue = state.v;
1276 }
1277
1278 if( att.item.$getElt() == null ) {
1279 // the thumbnail may be destroyed since the start of the animation
1280 return;
1281 }
1282 if( att.hoverIn && !att.item.hovered ) {
1283 // thumbnail no more hovered
1284 return;
1285 }
1286
1287 if( att.G.VOM.viewerDisplayed ) {
1288 return;
1289 }
1290
1291 // window.ng_draf( function() {
1292 switch( att.cssKind ) {
1293 case 'transform':
1294 att.item.CSSTransformSet(att.effect.element, att.effect.type, att.animeTo);
1295 att.item.CSSTransformApply(att.effect.element);
1296 break;
1297 case 'filter':
1298 att.item.CSSFilterSet(att.effect.element, att.effect.type, att.animeTo);
1299 att.item.CSSFilterApply(att.effect.element);
1300 break;
1301 default:
1302 att.item.$getElt(att.effect.element).css(att.effect.type, att.animeTo);
1303 break;
1304 }
1305 // });
1306 }
1307 });
1308 };
1309
1310 return NGY2Item;
1311 })();
1312
1313 }
1314
1315 _this.options = jQuery.extend(true, {}, jQuery.nanogallery2.defaultOptions, options);
1316 // Initialization code
1317 _this.nG2 = null;
1318 _this.nG2 = new nanoGALLERY2();
1319 _this.nG2.initiateGallery2(_this.e, _this.options );
1320
1321 };
1322
1323 // PUBLIC EXPOSED METHODS
1324 _this.test = function() {
1325 //alert('test');
1326 // console.dir(_this.nG.G.I.length);
1327 // console.dir(_this.nG);
1328 // debugger;
1329 //privateTest();
1330 }
1331
1332
1333 // Run initializer
1334 _this.init();
1335 };
1336
1337 jQuery.nanogallery2.defaultOptions = {
1338 kind : '',
1339 userID : '',
1340 photoset : '',
1341 album: '',
1342 blockList : 'scrapbook|profil|auto backup',
1343 tagBlockList: '',
1344 allowList : '',
1345 albumList : '',
1346 albumList2 : null,
1347 RTL : false,
1348 flickrSkipOriginal : true,
1349 breadcrumbAutoHideTopLevel : true,
1350 displayBreadcrumb : true,
1351 breadcrumbOnlyCurrentLevel : true,
1352 breadcrumbHideIcons : true,
1353 theme : 'nGY2',
1354 galleryTheme : 'dark',
1355 viewerTheme : 'dark',
1356 items : null,
1357 itemsBaseURL : '',
1358 thumbnailSelectable : false,
1359 dataProvider: '',
1360 allowHTMLinData: false,
1361 locationHash : true,
1362 slideshowDelay : 3000,
1363 slideshowAutoStart : false,
1364
1365 debugMode: false,
1366
1367 titleTranslationMap: null,
1368 galleryDisplayMoreStep : 2,
1369 galleryDisplayMode : 'fullContent',
1370 galleryL1DisplayMode : null,
1371 galleryPaginationMode : 'rectangles', // 'dots', 'rectangles', 'numbers'
1372 galleryPaginationTopButtons : true,
1373 galleryMaxRows : 2,
1374 galleryL1MaxRows : null,
1375 galleryLastRowFull: false,
1376 galleryL1LastRowFull: null,
1377 galleryLayoutEngine : 'default',
1378 paginationSwipe: true,
1379 paginationVisiblePages : 10,
1380 galleryFilterTags : false, // possible values: false, true, 'title', 'description'
1381 galleryL1FilterTags : null, // possible values: false, true, 'title', 'description'
1382 galleryFilterTagsMode : 'single',
1383 galleryL1FilterTagsMode : null,
1384 galleryMaxItems : 0, // maximum number of items per album --> only flickr, google2, nano_photos_provider2
1385 galleryL1MaxItems : null, // maximum number of items per gallery page --> only flickr, google2, nano_photos_provider2
1386 gallerySorting : '',
1387 galleryL1Sorting : null,
1388 galleryDisplayTransition : 'none',
1389 galleryL1DisplayTransition : null,
1390 galleryDisplayTransitionDuration : 1000,
1391 galleryL1DisplayTransitionDuration : null,
1392 galleryResizeAnimation : true,
1393 galleryRenderDelay : 30,
1394
1395 thumbnailCrop : true,
1396 thumbnailL1Crop : null,
1397 thumbnailCropScaleFactor : 1.5,
1398 thumbnailLevelUp : false,
1399 thumbnailAlignment : 'fillWidth',
1400 thumbnailWidth : 300,
1401 thumbnailL1Width : null,
1402 thumbnailHeight : 200,
1403 thumbnailL1Height : null,
1404 thumbnailBaseGridHeight : 0,
1405 thumbnailL1BaseGridHeight : null,
1406 thumbnailGutterWidth : 2,
1407 thumbnailL1GutterWidth : null,
1408 thumbnailGutterHeight : 2,
1409 thumbnailL1GutterHeight : null,
1410 thumbnailBorderVertical : 2,
1411 thumbnailL1BorderVertical : null,
1412 thumbnailBorderHorizontal : 2,
1413 thumbnailL1BorderHorizontal : null,
1414 thumbnailFeaturedKeyword : '*featured',
1415 thumbnailAlbumDisplayImage : false,
1416 thumbnailHoverEffect2 : 'toolsAppear',
1417 thumbnailBuildInit2 : '',
1418 thumbnailStacks : 0,
1419 thumbnailL1Stacks : null,
1420 thumbnailStacksTranslateX : 0,
1421 thumbnailL1StacksTranslateX : null,
1422 thumbnailStacksTranslateY : 0,
1423 thumbnailL1StacksTranslateY : null,
1424 thumbnailStacksTranslateZ : 0,
1425 thumbnailL1StacksTranslateZ : null,
1426 thumbnailStacksRotateX : 0,
1427 thumbnailL1StacksRotateX : null,
1428 thumbnailStacksRotateY : 0,
1429 thumbnailL1StacksRotateY : null,
1430 thumbnailStacksRotateZ : 0,
1431 thumbnailL1StacksRotateZ : null,
1432 thumbnailStacksScale : 0,
1433 thumbnailL1StacksScale : null,
1434 thumbnailDisplayOutsideScreen: true,
1435 thumbnailWaitImageLoaded: true,
1436 thumbnailSliderDelay: 2000,
1437 galleryBuildInit2 : '',
1438 portable : false,
1439 eventsDebounceDelay: 30,
1440
1441 touchAnimation : false,
1442 touchAnimationL1 : undefined,
1443 touchAutoOpenDelay : 0,
1444
1445 thumbnailLabel : {
1446 position : 'overImage',
1447 align: 'center',
1448 valign: 'bottom',
1449 display : true,
1450 displayDescription : false,
1451 titleMaxLength : 0,
1452 titleMultiLine : false,
1453 descriptionMaxLength : 0,
1454 descriptionMultiLine : false,
1455 hideIcons : true,
1456 title : ''
1457 },
1458
1459 thumbnailToolbarImage : { topLeft: 'select', topRight : 'featured' },
1460 thumbnailToolbarAlbum : { topLeft: 'select', topRight : 'counter' },
1461 thumbnailDisplayOrder : '',
1462 thumbnailL1DisplayOrder : null,
1463 thumbnailDisplayInterval : 15,
1464 thumbnailL1DisplayInterval : null,
1465 thumbnailDisplayTransition : 'fadeIn',
1466 thumbnailL1DisplayTransition : null,
1467 thumbnailDisplayTransitionEasing : 'easeOutQuart',
1468 thumbnailL1DisplayTransitionEasing : null,
1469 thumbnailDisplayTransitionDuration: 240,
1470 thumbnailL1DisplayTransitionDuration: null,
1471 thumbnailOpenInLightox : true,
1472 thumbnailOpenOriginal : false,
1473
1474 lightboxStandalone: false,
1475 viewer : 'internal',
1476 viewerFullscreen: false,
1477 imageTransition : 'swipe2',
1478 viewerTransitionMediaKind : 'img',
1479 viewerZoom : true,
1480 viewerImageDisplay : '',
1481 openOnStart : '',
1482 viewerHideToolsDelay : 4000,
1483 viewerToolbar : {
1484 display : false,
1485 position : 'bottom',
1486 fullWidth : false,
1487 align : 'center',
1488 autoMinimize : 0,
1489 standard : 'minimizeButton,label',
1490 minimized : 'minimizeButton,label,infoButton,shareButton,fullscreenButton'
1491 },
1492 viewerTools : {
1493 topLeft : 'pageCounter,playPauseButton',
1494 topRight : 'rotateLeft,rotateRight,fullscreenButton,closeButton'
1495 },
1496 viewerGallery: 'bottomOverMedia',
1497 viewerGalleryTWidth: 40,
1498 viewerGalleryTHeight: 40,
1499
1500 breakpointSizeSM : 480,
1501 breakpointSizeME : 992,
1502 breakpointSizeLA : 1200,
1503 breakpointSizeXL : 1800,
1504
1505 fnThumbnailInit : null,
1506 fnThumbnailHoverInit : null,
1507 fnThumbnailHover : null,
1508 fnThumbnailHoverOut : null,
1509 fnThumbnailDisplayEffect : null,
1510 fnViewerInfo : null,
1511 fnImgToolbarCustInit : null,
1512 fnImgToolbarCustDisplay : null,
1513 fnImgToolbarCustClick : null,
1514 fnProcessData : null,
1515 fnThumbnailSelection : null,
1516 fnGalleryRenderStart : null,
1517 fnGalleryRenderEnd : null,
1518 fnGalleryObjectModelBuilt : null,
1519 fnGalleryLayoutApplied : null,
1520 fnThumbnailClicked : null,
1521 fnShoppingCartUpdated : null,
1522 fnThumbnailToolCustAction : null,
1523 fnThumbnailOpen : null,
1524 fnImgDisplayed : null,
1525 fnPopupMediaInfo : null,
1526
1527 i18n : {
1528 'breadcrumbHome' : 'Galleries', 'breadcrumbHome_FR' : 'Galeries',
1529 'thumbnailImageTitle' : '', 'thumbnailAlbumTitle' : '',
1530 'thumbnailImageDescription' : '', 'thumbnailAlbumDescription' : '',
1531 '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'
1532 },
1533 icons : {
1534 // example for font awesome: <i style="color:#eee;" class="fa fa-search-plus"></i>
1535 thumbnailAlbum: '<i class="nGY2Icon-folder-empty"></i>',
1536 thumbnailImage: '<i class="nGY2Icon-picture"></i>',
1537 breadcrumbAlbum: '<i class="nGY2Icon-folder-empty"></i>',
1538 breadcrumbHome: '<i class="nGY2Icon-home"></i>',
1539 breadcrumbSeparator: '<i class="nGY2Icon-left-open"></i>',
1540 breadcrumbSeparatorRtl: '<i class="nGY2Icon-right-open"></i>',
1541 navigationFilterSelected: '<i style="color:#fff;" class="nGY2Icon-ok"></i>',
1542 navigationFilterUnselected: '<i style="color:#ddd;opacity:0.3;" class="nGY2Icon-circle-empty"></i>',
1543 navigationFilterSelectedAll: '<i class="nGY2Icon-ccw"></i>',
1544 navigationPaginationPrevious: '<i class="nGY2Icon-ngy2_chevron-left"></i>',
1545 navigationPaginationNext: '<i class="nGY2Icon-ngy2_chevron-right"></i>',
1546 thumbnailSelected: '<i style="color:#bff;" class="nGY2Icon-ok-circled"></i>',
1547 thumbnailUnselected: '<i style="color:#bff;" class="nGY2Icon-circle-empty"></i>',
1548 thumbnailFeatured: '<i style="color:#dd5;" class="nGY2Icon-star"></i>',
1549 thumbnailCounter: '<i class="nGY2Icon-picture"></i>',
1550 thumbnailShare: '<i class="nGY2Icon-ngy2_share2"></i>',
1551 thumbnailDownload: '<i class="nGY2Icon-ngy2_download2"></i>',
1552 thumbnailInfo: '<i class="nGY2Icon-ngy2_info2"></i>',
1553 thumbnailShoppingcart: '<i class="nGY2Icon-basket"></i>',
1554 thumbnailDisplay: '<i class="nGY2Icon-resize-full"></i>',
1555 thumbnailCustomTool1: 'T1',
1556 thumbnailCustomTool2: 'T2',
1557 thumbnailCustomTool3: 'T3',
1558 thumbnailCustomTool4: 'T4',
1559 thumbnailCustomTool5: 'T5',
1560 thumbnailCustomTool6: 'T6',
1561 thumbnailCustomTool7: 'T7',
1562 thumbnailCustomTool8: 'T8',
1563 thumbnailCustomTool9: 'T9',
1564 thumbnailCustomTool10: 'T10',
1565 thumbnailAlbumUp: '<i style="font-size: 3em;" class="nGY2Icon-ngy2_chevron_up2"></i>',
1566 paginationNext: '<i class="nGY2Icon-right-open"></i>',
1567 paginationPrevious: '<i class="nGY2Icon-left-open"></i>',
1568 galleryMoreButton: '<i class="nGY2Icon-picture"></i> &nbsp; <i class="nGY2Icon-right-open"></i>',
1569 buttonClose: '<i class="nGY2Icon-ngy2_close2"></i>',
1570 viewerPrevious: '<i class="nGY2Icon-ngy2_chevron-left"></i>',
1571 viewerNext: '<i class="nGY2Icon-ngy2_chevron-right"></i>',
1572 viewerImgPrevious: '<i class="nGY2Icon-ngy2_chevron_left3"></i>',
1573 viewerImgNext: '<i class="nGY2Icon-ngy2_chevron_right3"></i>',
1574 viewerDownload: '<i class="nGY2Icon-ngy2_download2"></i>',
1575 viewerToolbarMin: '<i class="nGY2Icon-ellipsis-vert"></i>',
1576 viewerToolbarStd: '<i class="nGY2Icon-menu"></i>',
1577 viewerPlay: '<i class="nGY2Icon-play"></i>',
1578 viewerPause: '<i class="nGY2Icon-pause"></i>',
1579 viewerFullscreenOn: '<i class="nGY2Icon-resize-full"></i>',
1580 viewerFullscreenOff: '<i class="nGY2Icon-resize-small"></i>',
1581 viewerZoomIn: '<i class="nGY2Icon-ngy2_zoom_in2"></i>',
1582 viewerZoomOut: '<i class="nGY2Icon-ngy2_zoom_out2"></i>',
1583 viewerLinkOriginal: '<i class="nGY2Icon-ngy2_external2"></i>',
1584 viewerInfo: '<i class="nGY2Icon-ngy2_info2"></i>',
1585 viewerShare: '<i class="nGY2Icon-ngy2_share2"></i>',
1586 viewerRotateLeft: '<i class="nGY2Icon-ccw"></i>',
1587 viewerRotateRight: '<i class="nGY2Icon-cw"></i>',
1588 viewerShoppingcart: '<i class="nGY2Icon-basket"></i>',
1589 user: '<i class="nGY2Icon-user"></i>',
1590 location: '<i class="nGY2Icon-location"></i>',
1591 picture: '<i class="nGY2Icon-picture"></i>',
1592 config: '<i class="nGY2Icon-wrench"></i>',
1593 shareFacebook: '<i style="color:#3b5998;" class="nGY2Icon-facebook-squared"></i>',
1594 shareTwitter: '<i style="color:#00aced;" class="nGY2Icon-twitter-squared"></i>',
1595 // shareGooglePlus: '<i style="color:#dd4b39;" class="nGY2Icon-gplus-squared"></i>',
1596 shareTumblr: '<i style="color:#32506d;" class="nGY2Icon-tumblr-squared"></i>',
1597 sharePinterest: '<i style="color:#cb2027;" class="nGY2Icon-pinterest-squared"></i>',
1598 shareVK: '<i style="color:#3b5998;" class="nGY2Icon-vkontakte"></i>',
1599 shareMail: '<i style="color:#555;" class="nGY2Icon-mail-alt"></i>',
1600 viewerCustomTool1: 'T1',
1601 viewerCustomTool2: 'T2',
1602 viewerCustomTool3: 'T3',
1603 viewerCustomTool4: 'T4',
1604 viewerCustomTool5: 'T5',
1605 viewerCustomTool6: 'T6',
1606 viewerCustomTool7: 'T7',
1607 viewerCustomTool8: 'T8',
1608 viewerCustomTool9: 'T9',
1609 viewerCustomTool10: 'T10'
1610 }
1611 };
1612
1613 jQuery.fn.nanogallery2 = function (args, option, value) {
1614
1615 if( typeof jQuery(this).data('nanogallery2data') === 'undefined'){
1616 if( args == 'destroy' ) {
1617 // command to destroy but no instance yet --> exit
1618 return;
1619 }
1620
1621 return this.each( function(){
1622 (new jQuery.nanogallery2(this, args));
1623 });
1624 }
1625 else {
1626 // no options -->
1627 // This function breaks the chain, but provides some API methods
1628 var nG2 = $(this).data('nanogallery2data').nG2;
1629
1630 // Lightbox standalone
1631 // (Another click on an already opened media)
1632 if( args !== undefined && args.lightboxStandalone === true ) {
1633 // items exist already (G.I is populated) -> just open the lightbox again
1634 nG2.LightboxReOpen();
1635 return;
1636 }
1637
1638 switch(args){
1639 case 'displayItem':
1640 nG2.DisplayItem(option);
1641 break;
1642
1643 case 'search':
1644 return( nG2.Search(option));
1645 break;
1646
1647 case 'search2':
1648 return nG2.Search2(option, value);
1649 break;
1650
1651 case 'search2Execute':
1652 return nG2.Search2Execute();
1653 break;
1654
1655 case 'refresh':
1656 nG2.Refresh();
1657 break;
1658
1659 case 'resize':
1660 nG2.Resize();
1661 break;
1662
1663 case 'instance':
1664 return nG2;
1665 break;
1666
1667 case 'data':
1668 nG2.data= {
1669 items: nG2.I,
1670 gallery: nG2.GOM,
1671 lightbox: nG2.VOM,
1672 shoppingcart: nG2.shoppingCart
1673 };
1674 return nG2.data;
1675 break;
1676
1677 case 'reload':
1678 nG2.ReloadAlbum();
1679 return $(this);
1680 break;
1681
1682 case 'itemsSelectedGet':
1683 return nG2.ItemsSelectedGet();
1684 break;
1685
1686 case 'itemsSetSelectedValue':
1687 nG2.ItemsSetSelectedValue(option, value);
1688 break;
1689
1690 case 'option':
1691 if(typeof value === 'undefined'){
1692 return nG2.Get(option);
1693 }else{
1694 nG2.Set(option,value);
1695 if( option == 'demoViewportWidth' ) {
1696 // force resize event -> for demo purposes
1697 $(window).trigger('resize');
1698 }
1699 }
1700 break;
1701
1702 case 'destroy':
1703 nG2.Destroy();
1704 $(this).removeData('nanogallery2data');
1705 break;
1706
1707 case 'shoppingCartGet':
1708 // returns the content of the shoppingcart
1709 return nG2.shoppingCart;
1710 break;
1711
1712 case 'shoppingCartUpdate':
1713 // parameters :
1714 // - option = item's ID
1715 // - value = new quantity
1716
1717 if( typeof value === 'undefined' || typeof option === 'undefined' ){
1718 return false;
1719 }
1720
1721 var item_ID = option;
1722 var new_qty = value;
1723
1724 for( var i=0; i < nG2.shoppingCart.length; i++) {
1725 if( nG2.shoppingCart[i].ID == item_ID ) {
1726
1727 // updates counter
1728 nG2.shoppingCart[i].qty = new_qty;
1729
1730 let item = nG2.I[nG2.shoppingCart[i].idx];
1731
1732 // updates thumbnail
1733 nG2.ThumbnailToolbarOneCartUpdate( item );
1734
1735 if( new_qty == 0 ) {
1736 // removes item from shoppingcart
1737 nG2.shoppingCart.splice(i, 1);
1738 }
1739
1740 var fu = nG2.O.fnShoppingCartUpdated;
1741 if( fu !== null ) {
1742 typeof fu == 'function' ? fu(nG2.shoppingCart, item, 'api') : window[fu](nG2.shoppingCart, item, 'api');
1743 }
1744
1745 break;
1746 }
1747 }
1748
1749 return nG2.shoppingCart;
1750 break;
1751
1752 case 'shoppingCartRemove':
1753 // parameters :
1754 // - option = item's ID
1755 if( typeof option === 'undefined' ){
1756 return false;
1757 }
1758 var ID = option;
1759 for( var i=0; i < nG2.shoppingCart.length; i++) {
1760 if( nG2.shoppingCart[i].ID == ID ) {
1761
1762 var item = nG2.I[nG2.shoppingCart[i].idx];
1763
1764 // updates thumbnail
1765 nG2.shoppingCart[i].qty = 0;
1766 nG2.ThumbnailToolbarOneCartUpdate( item );
1767
1768 // removes item from shoppingcart
1769 nG2.shoppingCart.splice(i, 1);
1770
1771
1772 var fu = nG2.O.fnShoppingCartUpdated;
1773 if( fu !== null ) {
1774 typeof fu == 'function' ? fu(nG2.shoppingCart, item, 'api') : window[fu](nG2.shoppingCart, item, 'api');
1775 }
1776
1777 break;
1778 }
1779 }
1780
1781 return nG2.shoppingCart;
1782 break;
1783
1784 case 'closeViewer':
1785 nG2.CloseViewer();
1786 break;
1787 case 'minimizeToolbar':
1788 nG2.MinimizeToolbar();
1789 break;
1790 case 'maximizeToolbar':
1791 nG2.MaximizeToolbar();
1792 break;
1793 case 'paginationPreviousPage':
1794 nG2.PaginationPreviousPage();
1795 break;
1796 case 'paginationNextPage':
1797 nG2.paginationNextPage();
1798 break;
1799 case 'paginationGotoPage':
1800 nG2.PaginationGotoPage( option );
1801 break;
1802 case 'paginationCountPages':
1803 nG2.PaginationCountPages();
1804 break;
1805
1806 }
1807 return $(this);
1808
1809 }
1810 };
1811
1812
1813 // ###############################
1814 // ##### nanogallery2 script #####
1815 // ###############################
1816
1817 /** @function nanoGALLERY2 */
1818 function nanoGALLERY2() {
1819 "use strict";
1820
1821 /**
1822 * Force reload the current album, if provided by Json
1823 */
1824 this.LightboxReOpen = function(){
1825 LightboxStandaloneDisplay();
1826 }
1827
1828 /**
1829 * Force reload the current album, if provided by Json
1830 */
1831 this.ReloadAlbum = function(){
1832 if( G.O.kind === '' ) {
1833 throw 'Not supported for this content source:' + G.O.kind;
1834 }
1835
1836 var albumIdx = G.GOM.albumIdx;
1837 if( albumIdx == -1 ) {
1838 throw ('Current album not found.');
1839 }
1840
1841 var albumID = G.I[albumIdx].GetID();
1842
1843 // unselect everything & remove link to album (=logical delete)
1844 var l = G.I.length;
1845 for( var i = 0; i < l ; i++ ) {
1846 var item = G.I[i];
1847 if( item.albumID == albumID ) {
1848 item.selected = false;
1849 }
1850 }
1851
1852 G.I[albumIdx].contentIsLoaded = false;
1853
1854 DisplayAlbum('-1', albumID);
1855 };
1856
1857 /**
1858 * Set one or several items selected/unselected
1859 * @param {array} items
1860 */
1861 this.ItemsSetSelectedValue = function(items, value){
1862 var l = items.length;
1863 for( var j = 0; j < l ; j++) {
1864 ThumbnailSelectionSet(items[j], value);
1865 }
1866 };
1867
1868 /**
1869 * Returns an array of selected items
1870 * @returns {Array}
1871 */
1872 this.ItemsSelectedGet = function(){
1873 var selectedItems = [];
1874 var l = G.I.length;
1875 for( var i = 0; i < l ; i++ ) {
1876 if( G.I[i].selected == true ) {
1877 selectedItems.push(G.I[i]);
1878 }
1879 }
1880 return selectedItems;
1881 };
1882
1883 /**
1884 * Returns the value of an option
1885 * @param {string} option
1886 * @returns {nanoGALLERY.G.O}
1887 */
1888 this.Get = function(option){
1889 return G.O[option];
1890 };
1891
1892 /**
1893 * Set a new value for a defined option
1894 * @param {string} option
1895 */
1896 this.Set = function(option, value){
1897 G.O[option] = value;
1898 switch( option ) {
1899 case 'thumbnailSelectable':
1900 ThumbnailSelectionClear();
1901 // refresh the displayed gallery
1902 GalleryRender( G.GOM.albumIdx );
1903 break;
1904 }
1905 };
1906
1907 /**
1908 * Refresh the current gallery
1909 */
1910 this.Refresh = function() {
1911 // Refresh the displayed gallery
1912 GalleryRender( G.GOM.albumIdx );
1913 };
1914 /**
1915 * Resize the current gallery
1916 */
1917 this.Resize = function() {
1918 // resize the displayed gallery
1919 GalleryResize();
1920 };
1921
1922 /**
1923 * display one item (image or gallery)
1924 * itemID syntax:
1925 * - albumID --> display one album
1926 * - albumID/imageID --> display one image
1927 */
1928 this.DisplayItem = function( itemID ) {
1929 var IDs=parseIDs( itemID );
1930 if( IDs.imageID != '0' ) {
1931 DisplayPhoto( IDs.imageID, IDs.albumID );
1932 }
1933 else {
1934 DisplayAlbum( '-1', IDs.albumID );
1935 }
1936 };
1937
1938 this.ThumbnailToolbarOneCartUpdate = function ( item ) {
1939 ThumbnailBuildToolbarOneCartUpdate( item );
1940 }
1941
1942
1943
1944 var CountItemsToDisplay = function( gIdx ) {
1945 if( G.I[gIdx] == undefined ) { return 0; }
1946 var albumID = G.I[gIdx].GetID();
1947 var l = G.I.length;
1948 var cnt = 0;
1949 for( var idx = 0; idx < l; idx++ ) {
1950 var item = G.I[idx];
1951 if( item.isToDisplay(albumID) ) {
1952 cnt++;
1953 }
1954 }
1955 return cnt;
1956 }
1957 /**
1958 * Search in the displayed gallery (in thumbnails title)
1959 */
1960 this.Search = function( search ) {
1961 G.GOM.albumSearch = search.toUpperCase();
1962 var gIdx = G.GOM.albumIdx;
1963 GalleryRender( G.GOM.albumIdx );
1964 return CountItemsToDisplay( gIdx );
1965 };
1966
1967 /**
1968 * Search2 in title and tags - set search values
1969 */
1970 this.Search2 = function( searchTitle, searchTags ) {
1971 if( searchTitle != undefined && searchTitle != null ) {
1972 G.GOM.albumSearch = searchTitle.toUpperCase().trim();
1973 }
1974 else {
1975 G.GOM.albumSearch = '';
1976 }
1977
1978 if( searchTags != null && searchTags != undefined ) {
1979 G.GOM.albumSearchTags = searchTags.toUpperCase().trim();
1980 }
1981 else {
1982 G.GOM.albumSearchTags = '';
1983 }
1984 return CountItemsToDisplay( G.GOM.albumIdx );
1985 };
1986
1987 /**
1988 * Search2 - execute the search on title and tags
1989 */
1990 this.Search2Execute = function() {
1991 var gIdx = G.GOM.albumIdx;
1992 GalleryRender( G.GOM.albumIdx );
1993 return CountItemsToDisplay( gIdx );
1994 };
1995
1996
1997 /**
1998 * Destroy the current gallery
1999 */
2000 this.Destroy = function(){
2001
2002 if( G.GOM.hammertime != null ) {
2003 G.GOM.hammertime.destroy();
2004 G.GOM.hammertime = null;
2005 }
2006
2007 if( G.VOM.hammertime != null ) {
2008 G.VOM.hammertime.destroy();
2009 G.VOM.hammertime = null;
2010 }
2011
2012 // color scheme
2013 $('#ngycs_' + G.baseEltID).remove()
2014
2015 G.GOM.items = [];
2016 NGY2Item.New( G, G.i18nTranslations.breadcrumbHome, '', '0', '-1', 'album' );
2017 G.GOM.navigationBar.$newContent = null;
2018 G.$E.base.empty();
2019 G.$E.base.removeData();
2020 if( G.O.locationHash ) {
2021 jQuery(window).off('hashchange.nanogallery2.' + G.baseEltID);
2022 }
2023
2024 jQuery(window).off('resize.nanogallery2.' + G.baseEltID);
2025 jQuery(window).off('orientationChange.nanogallery2.' + G.baseEltID);
2026 jQuery(window).off('scroll.nanogallery2.' + G.baseEltID);
2027 if( G.$E.scrollableParent !== null ) {
2028 G.$E.scrollableParent.off('scroll.nanogallery2.' + G.baseEltID);
2029 }
2030 G.GOM.firstDisplay = true;
2031 };
2032
2033 /**
2034 * CloseViewer - close the media viewer
2035 */
2036 this.CloseViewer = function() {
2037 LightboxClose(null);
2038 return false;
2039 };
2040
2041 /**
2042 * MinimizeToolbar - display the minimized lightbox main toolbar
2043 */
2044 this.MinimizeToolbar = function() {
2045 ViewerToolbarForVisibilityMin();
2046 return false;
2047 };
2048
2049 /**
2050 * MaximizeToolbar - display the maximized/standard lightbox main toolbar
2051 */
2052 this.MaximizeToolbar = function() {
2053 ViewerToolbarForVisibilityStd();
2054 return false;
2055 };
2056
2057 /**
2058 * PaginationPreviousPage - gallery paginate to previous page
2059 */
2060 this.PaginationPreviousPage = function() {
2061 paginationPreviousPage();
2062 return false;
2063 };
2064
2065
2066 /**
2067 * PaginationNextPage - gallery paginate to next page
2068 */
2069 this.PaginationNextPage = function() {
2070 paginationNextPage();
2071 return false;
2072 };
2073
2074
2075 /**
2076 * PaginationGotoPage - gallery paginate to specific page
2077 */
2078 this.PaginationGotoPage = function( page ) {
2079 // var aIdx = G.$E.conPagin.data('galleryIdx');
2080 if( page > 1 ) { page--; }
2081 G.GOM.pagination.currentPage = page;
2082
2083 // scroll to top of gallery if not displayed
2084 G.GOM.ScrollToTop();
2085
2086 GalleryDisplayPart1();
2087 GalleryDisplayPart2( true );
2088 return false;
2089 };
2090
2091 /**
2092 * PaginationCountPages - gallery pagination - returns the number of pages
2093 */
2094 this.PaginationCountPages = function() {
2095 if( G.GOM.items.length == 0 ) { return 0; } // no thumbnail to display
2096
2097 var nbPages = Math.ceil((G.GOM.items[G.GOM.items.length - 1].row + 1) / G.galleryMaxRows.Get());
2098 return nbPages;
2099 };
2100
2101 /**
2102 * PaginationCountPages - gallery pagination - returns the number of pages
2103 */
2104
2105
2106
2107 // throttle()
2108 // author: underscore.js - http://underscorejs.org/docs/underscore.html
2109 // Returns a function, that, when invoked, will only be triggered at most once during a given window of time.
2110 // Normally, the throttled function will run as much as it can, without ever going more than once per wait duration;
2111 // but if you�d like to disable the execution on the leading edge, pass {leading: false}.
2112 // To disable execution on the trailing edge, ditto.
2113 var throttle = function(func, wait, options) {
2114 var context, args, result;
2115 var timeout = null;
2116 var previous = 0;
2117 if (!options) options = {};
2118 var later = function() {
2119 previous = options.leading === false ? 0 : new Date().getTime();
2120 timeout = null;
2121 result = func.apply(context, args);
2122 if (!timeout) context = args = null;
2123 };
2124 return function() {
2125 var now = new Date().getTime();
2126 if (!previous && options.leading === false) previous = now;
2127 var remaining = wait - (now - previous);
2128 context = this;
2129 args = arguments;
2130 if (remaining <= 0 || remaining > wait) {
2131 if (timeout) {
2132 clearTimeout(timeout);
2133 timeout = null;
2134 }
2135 previous = now;
2136 result = func.apply(context, args);
2137 if (!timeout) context = args = null;
2138 } else if (!timeout && options.trailing !== false) {
2139 timeout = setTimeout(later, remaining);
2140 }
2141 return result;
2142 };
2143 };
2144
2145
2146 // DEBOUNCE
2147 // author: John Hann - http://unscriptable.com/2009/03/20/debouncing-javascript-methods/
2148 // execAsap - false means executing at the end of the detection period
2149 var debounce = function (func, threshold, execAsap) {
2150 var timeout;
2151 return function debounced () {
2152 var obj = this, args = arguments;
2153 function delayed () {
2154 if (!execAsap)
2155 func.apply(obj, args);
2156 timeout = null;
2157 };
2158
2159 if (timeout)
2160 clearTimeout(timeout);
2161 // clearRequestTimeout(timeout);
2162 else if (execAsap)
2163 func.apply(obj, args);
2164 timeout = setTimeout(delayed, threshold || 100);
2165 // timeout = requestTimeout(delayed, threshold || 100);
2166 };
2167 }
2168
2169
2170 // Double requestAnimationFrame
2171 window.ng_draf = function (cb) {
2172 return requestAnimationFrame(function() {
2173 window.requestAnimationFrame(cb)
2174 })
2175 }
2176
2177 // REQUESTTIMEOUT - replace SETTIMEOUT - https://gist.github.com/joelambert/1002116
2178 /**
2179 * Behaves the same as setTimeout except uses requestAnimationFrame() where possible for better performance
2180 * @param {function} fn The callback function
2181 * @param {int} delay The delay in milliseconds
2182 */
2183
2184 window.requestTimeout = function(fn, delay) {
2185 if( !window.requestAnimationFrame &&
2186 !window.webkitRequestAnimationFrame &&
2187 !(window.mozRequestAnimationFrame && window.mozCancelRequestAnimationFrame) && // Firefox 5 ships without cancel support
2188 !window.oRequestAnimationFrame &&
2189 !window.msRequestAnimationFrame)
2190 return window.setTimeout(fn, delay);
2191
2192 var start = new Date().getTime(),
2193 handle = new Object();
2194
2195 function loop(){
2196 var current = new Date().getTime(),
2197 delta = current - start;
2198 // delta = delay;
2199
2200 delta >= delay ? fn.call() : handle.value = requestAnimFrame(loop);
2201 };
2202
2203 handle.value = requestAnimFrame(loop);
2204 return handle;
2205 };
2206
2207
2208 // requestAnimationFrame() shim by Paul Irish
2209 // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
2210 window.requestAnimFrame = (function() {
2211 return window.requestAnimationFrame ||
2212 window.webkitRequestAnimationFrame ||
2213 window.mozRequestAnimationFrame ||
2214 window.oRequestAnimationFrame ||
2215 window.msRequestAnimationFrame ||
2216 function(/* function */ callback, /* DOMElement */ element){
2217 window.setTimeout(callback, 1000 / 60);
2218 };
2219 })();
2220
2221
2222 // CLEARREQUESTTIMEOUT - to replace CLEARTIMEOUT - https://gist.github.com/joelambert/1002116
2223 /**
2224 * Behaves the same as clearTimeout except uses cancelRequestAnimationFrame() where possible for better performance
2225 * @param {int|object} fn The callback function
2226 */
2227 window.clearRequestTimeout = function(handle) {
2228 window.cancelAnimationFrame ? window.cancelAnimationFrame(handle.value) :
2229 window.webkitCancelAnimationFrame ? window.webkitCancelAnimationFrame(handle.value) :
2230 window.webkitCancelRequestAnimationFrame ? window.webkitCancelRequestAnimationFrame(handle.value) : /* Support for legacy API */
2231 window.mozCancelRequestAnimationFrame ? window.mozCancelRequestAnimationFrame(handle.value) :
2232 window.oCancelRequestAnimationFrame ? window.oCancelRequestAnimationFrame(handle.value) :
2233 window.msCancelRequestAnimationFrame ? window.msCancelRequestAnimationFrame(handle.value) :
2234 clearTimeout(handle);
2235 };
2236
2237
2238
2239 /*
2240 ** Global data for this nanogallery2 instance
2241 **/
2242 var G=this;
2243 G.I = []; // gallery items
2244 G.Id = []; // gallery items
2245 G.O = null; // user options
2246 G.baseEltID = null; // ID of the base element
2247 G.$E = {
2248 base: null, // base element
2249 conTnParent: null, // $g_containerThumbnailsParent
2250 conLoadingB: null, // loading bar - nanoGalleryLBarOff
2251 conConsole: null, // console for error messages
2252 conNavigationBar: null, // gallery navigation bar
2253 conTnBottom: null, // container on the bottom of the gallery
2254 scrollableParent: null // first scrollable parent container
2255 };
2256 G.shoppingCart = [];
2257 G.layout = { // Layout informations
2258 internal : true,
2259 engine : '',
2260 support : { rows: false },
2261 prerequisite : { imageSize: false },
2262 SetEngine: function() {
2263
2264 if( G.layout.internal ) {
2265 if( G.tn.settings.width[G.GOM.curNavLevel][G.GOM.curWidth] == 'auto' || G.tn.settings.width[G.GOM.curNavLevel][G.GOM.curWidth] == '' ) {
2266 // do not use getH() / getW() here!
2267 G.layout.engine = 'JUSTIFIED';
2268 G.layout.support.rows = true;
2269 G.layout.prerequisite.imageSize = true;
2270 return;
2271 }
2272 if( G.tn.settings.height[G.GOM.curNavLevel][G.GOM.curWidth] == 'auto' || G.tn.settings.height[G.GOM.curNavLevel][G.GOM.curWidth] == '' ) {
2273 // do not use getH() / getW() here!
2274 G.layout.engine = 'CASCADING';
2275 G.layout.support.rows = false;
2276 G.layout.prerequisite.imageSize = true;
2277 return;
2278 }
2279
2280 if( G.tn.settings.getMosaic() != null ) {
2281 G.layout.engine = 'MOSAIC';
2282 G.layout.support.rows = true;
2283 G.layout.prerequisite.imageSize = false;
2284 return;
2285 }
2286
2287 G.layout.engine = 'GRID';
2288 G.layout.support.rows=true;
2289 // if( G.tn.opt.Get('crop') === true ) {
2290 // G.layout.prerequisite.imageSize = true;
2291 // }
2292 // else {
2293 G.layout.prerequisite.imageSize = false;
2294 // }
2295 }
2296 }
2297 };
2298 G.galleryResizeEventEnabled = false;
2299 G.galleryMaxRows = { l1: 0, lN: 0,
2300 Get: function() {
2301 return G.galleryMaxRows[G.GOM.curNavLevel];
2302 }
2303 };
2304 G.galleryMaxItems = { l1: 0, lN: 0,
2305 Get: function() {
2306 return G.galleryMaxItems[G.GOM.curNavLevel];
2307 }
2308 };
2309 G.galleryFilterTags = { l1: 0, lN: 0,
2310 Get: function() {
2311 return G.galleryFilterTags[G.GOM.curNavLevel];
2312 }
2313 };
2314 G.galleryFilterTagsMode = { l1: 0, lN: 0,
2315 Get: function() {
2316 return G.galleryFilterTagsMode[G.GOM.curNavLevel];
2317 }
2318 };
2319 G.galleryDisplayMode = { l1: 'FULLCONTENT', lN: 'FULLCONTENT',
2320 Get: function() {
2321 return G.galleryDisplayMode[G.GOM.curNavLevel];
2322 }
2323 };
2324 G.galleryLastRowFull = { l1: false, lN: false,
2325 Get: function() {
2326 return G.galleryLastRowFull[G.GOM.curNavLevel];
2327 }
2328 };
2329 G.gallerySorting = { l1: '', lN: '',
2330 Get: function() {
2331 return G.gallerySorting[G.GOM.curNavLevel];
2332 }
2333 };
2334 G.galleryDisplayTransition = { l1: 'none', lN: 'none',
2335 Get: function() {
2336 return G.galleryDisplayTransition[G.GOM.curNavLevel];
2337 }
2338 };
2339 G.galleryDisplayTransitionDuration = { l1: 500, lN: 500,
2340 Get: function() {
2341 return G.galleryDisplayTransitionDuration[G.GOM.curNavLevel];
2342 }
2343 };
2344 G.$currentTouchedThumbnail = null;
2345
2346
2347
2348
2349 // ##### GENERAL THUMBNAILS PROPERTIES -->
2350 G.tn = {
2351 // levell specific options
2352 opt: {
2353 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 },
2354 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 },
2355 Get: function(opt) {
2356 return G.tn.opt[G.GOM.curNavLevel][opt];
2357 }
2358 },
2359 scale: 1, // image scale depending of the hover effect
2360 labelHeight: { // in case label on bottom, otherwise always=0
2361 l1: 0, lN: 0,
2362 get: function() {
2363 return G.tn.labelHeight[G.GOM.curNavLevel];
2364 }
2365 },
2366 defaultSize: { // default thumbnail size
2367 // label height is not included
2368 width: { l1 : { xs:0, sm:0, me:0, la:0, xl:0 }, lN : { xs:0, sm:0, me:0, la:0, xl:0 } },
2369 height: { l1 : { xs:0, sm:0, me:0, la:0, xl:0 }, lN : { xs:0, sm:0, me:0, la:0, xl:0 } },
2370 getWidth: function() {
2371 return G.tn.defaultSize.width[G.GOM.curNavLevel][G.GOM.curWidth];
2372 },
2373 getOuterWidth: function() { // width including border
2374 G.tn.borderWidth = G.tn.opt.Get('borderHorizontal');
2375 G.tn.borderHeight = G.tn.opt.Get('borderVertical');
2376 var w = G.tn.defaultSize.width[G.GOM.curNavLevel][G.GOM.curWidth] + G.tn.opt.Get('borderHorizontal') * 2;
2377 if( G.O.thumbnailLabel.get('position') == 'right' || G.O.thumbnailLabel.get('position') == 'left' ) {
2378 w += G.tn.defaultSize.width[G.GOM.curNavLevel][G.GOM.curWidth];
2379 }
2380 return w;
2381 },
2382 getHeight: function() {
2383 return G.tn.defaultSize.height[G.GOM.curNavLevel][G.GOM.curWidth];
2384 },
2385 getOuterHeight: function() { // height, border included
2386 return G.tn.defaultSize.height[G.GOM.curNavLevel][G.GOM.curWidth]+G.tn.opt.Get('borderVertical')*2;
2387 }
2388 },
2389 settings: { // user defined width/height of the image to display depending on the screen size
2390 width: { l1 : { xs:0, sm:0, me:0, la:0, xl:0, xsc:'u', smc:'u', mec:'u', lac:'u', xlc:'u' },
2391 lN : { xs:0, sm:0, me:0, la:0, xl:0, xsc:'u', smc:'u', mec:'u', lac:'u', xlc:'u' } },
2392 height: { l1 : { xs:0, sm:0, me:0, la:0, xl:0, xsc:'u', smc:'u', mec:'u', lac:'u', xlc:'u' },
2393 lN : { xs:0, sm:0, me:0, la:0, xl:0, xsc:'u', smc:'u', mec:'u', lac:'u', xlc:'u' } },
2394 getH: function(l, w) {
2395 var cl = (l == undefined ? G.GOM.curNavLevel : l);
2396 var cw = (w == undefined ? G.GOM.curWidth : w);
2397 if( G.layout.engine == 'MOSAIC' ) {
2398 return this.height[cl][cw] * this.mosaic[cl+'Factor']['h'][cw];
2399 }
2400 else {
2401 return this.height[cl][cw];
2402 }
2403 },
2404 getW: function(l, w) {
2405 var cl = (l == undefined ? G.GOM.curNavLevel : l);
2406 var cw = (w == undefined ? G.GOM.curWidth : w);
2407 if( G.layout.engine == 'MOSAIC' ) {
2408 return this.width[cl][cw] * this.mosaic[cl+'Factor']['w'][cw];
2409 }
2410 else {
2411 return this.width[cl][cw];
2412 // return G.tn.settings.width[G.GOM.curNavLevel][G.GOM.curWidth];
2413 }
2414 },
2415 mosaic: { l1 : { xs: null, sm: null, me: null, la: null, xl: null },
2416 lN : { xs: null, sm: null, me: null, la: null, xl: null },
2417 l1Factor : { h :{ xs: 1, sm: 1, me: 1, la: 1, xl: 1 }, w :{ xs: 1, sm: 1, me: 1, la: 1, xl: 1 }},
2418 lNFactor : { h :{ xs: 1, sm: 1, me: 1, la: 1, xl: 1 }, w :{ xs: 1, sm: 1, me: 1, la: 1, xl: 1 }}
2419 },
2420 getMosaic: function() {
2421 return this.mosaic[G.GOM.curNavLevel][G.GOM.curWidth];
2422 },
2423 mosaicCalcFactor: function(l, w) {
2424 // retrieve max size multiplicator
2425 var maxW = 1;
2426 var maxH = 1;
2427 for( var n = 0; n < G.tn.settings.mosaic[l][w].length; n++ ) {
2428 maxW = Math.max(maxW, this.mosaic[l][w][n]['w']);
2429 maxH = Math.max(maxH, this.mosaic[l][w][n]['h']);
2430 }
2431 this.mosaic[l + 'Factor']['h'][w] = maxH;
2432 this.mosaic[l + 'Factor']['w'][w] = maxW;
2433 },
2434 gutterHeight: { l1 : { xs:0, sm:0, me:0, la:0, xl:0 }, lN : { xs:0, sm:0, me:0, la:0, xl:0 } },
2435 gutterWidth: { l1 : { xs:0, sm:0, me:0, la:0, xl:0 }, lN : { xs:0, sm:0, me:0, la:0, xl:0 } },
2436 GetResponsive: function( setting ) {
2437 return this[setting][G.GOM.curNavLevel][G.GOM.curWidth];
2438 }
2439 },
2440 // thumbnail hover effects
2441 hoverEffects : {
2442 std : [],
2443 level1: [],
2444 get: function() {
2445 if( G.GOM.curNavLevel == 'l1' && this.level1.length !== 0 ) {
2446 return this.level1;
2447 }
2448 else {
2449 return this.std;
2450 }
2451 }
2452 },
2453 // thumbnail init
2454 buildInit : {
2455 std : [],
2456 level1: [],
2457 get: function() {
2458 if( G.GOM.curNavLevel == 'l1' && this.level1.length !== 0 ) {
2459 return this.level1;
2460 }
2461 else {
2462 return this.std;
2463 }
2464 }
2465 },
2466 // thumbnail toolbars
2467 toolbar: {
2468 album : { topLeft : '', topRight: '', bottomLeft: '', bottomRight: '' },
2469 image : { topLeft : '', topRight: '', bottomLeft: '', bottomRight: '' },
2470 albumUp : { topLeft : '', topRight: '', bottomLeft: '', bottomRight: '' },
2471 get: function( item ) {
2472 return this[item.kind];
2473 },
2474 },
2475 style: {
2476 // inline CSS
2477 l1 : { label: '', title: '', desc: '' },
2478 lN : { label: '', title: '', desc: '' },
2479 getTitle : function() {
2480 return ('style="' + this[G.GOM.curNavLevel].title + '"');
2481 },
2482 getDesc : function() {
2483 return ('style="' + this[G.GOM.curNavLevel].desc + '"');
2484 },
2485 getLabel: function() {
2486 var s='style="'+ this[G.GOM.curNavLevel].label;
2487 s+= (G.O.RTL ? '"direction:RTL;"' :'');
2488 s+='"';
2489 return s;
2490 }
2491 }
2492 };
2493 G.scrollTimeOut = 0;
2494 G.i18nTranslations = {'paginationPrevious':'Previous', 'paginationNext':'Next', 'breadcrumbHome':'List of Albums', 'thumbnailImageTitle':'', 'thumbnailAlbumTitle':'', 'thumbnailImageDescription':'', 'thumbnailAlbumDescription':'' };
2495 G.emptyGif = 'data:image/gif;base64,R0lGODlhEAAQAIAAAP///////yH5BAEKAAEALAAAAAAQABAAAAIOjI+py+0Po5y02ouzPgUAOw==';
2496 G.CSStransformName = FirstSupportedPropertyName(["transform", "msTransform", "MozTransform", "WebkitTransform", "OTransform"]);
2497 // G.CSSfilterName = FirstSupportedPropertyName(["filter", "WebkitFilter"]);
2498 G.CSStransformStyle = FirstSupportedPropertyName(["transformStyle", "msTransformStyle", "MozTransformStyle", "WebkitTransformStyle", "OTransformStyle"]);
2499 G.CSSperspective = FirstSupportedPropertyName(["perspective", "msPerspective", "MozPerspective", "WebkitPerspective", "OPerspective"]);
2500 G.CSSbackfaceVisibilityName = FirstSupportedPropertyName(["backfaceVisibility", "msBackfaceVisibility", "MozBackfaceVisibility", "WebkitBackfaceVisibility", "OBackfaceVisibility"]);
2501 G.CSStransitionName = FirstSupportedPropertyName(["transition", "msTransition", "MozTransition", "WebkitTransition", "OTransition"]);
2502 G.CSSanimationName = FirstSupportedPropertyName(["animation", "msAnimation", "MozAnimation", "WebkitAnimation", "OAnimation"]);
2503 G.GalleryResizeThrottled = throttle(GalleryResize, 30, {leading: false});
2504
2505 G.blockList = null; // album names - block list
2506 G.allowList = null; // album names - allow list
2507 G.albumList = []; // album list
2508 G.locationHashLastUsed = '';
2509 G.custGlobals = {};
2510 G.touchAutoOpenDelayTimerID = 0;
2511 G.i18nLang = '';
2512 G.timeLastTouchStart = 0;
2513 G.custGlobals = {};
2514 G.markupOrApiProcessed = false;
2515
2516 //------------------------
2517 //--- Gallery Object Model
2518 G.GOM = {
2519 albumIdx : -1, // index (in G.I) of the currently displayed album
2520 clipArea : { top: 0, height: 0 }, // area of the GOM to display on screen
2521 displayArea : { width: 0 , height: 0 }, // size of the GOM area (=used area, not available area)
2522 displayAreaLast : { width: 0 , height: 0 }, // previous size of the GOM area
2523 displayedMoreSteps : 0, // current number of displayed steps (moreButton mode)
2524 items: [], // current items of the GOMS
2525 $imgPreloader: [],
2526 thumbnails2Display: [],
2527 itemsDisplayed : 0, // number of currently displayed thumbnails
2528 firstDisplay : true,
2529 firstDisplayTime : 0, // in conjunction with galleryRenderDelay
2530 navigationBar : { // content of the navigation bar (for breadcrumb, filter tags and next/previous pagination)
2531 displayed: false,
2532 $newContent: ''
2533 },
2534 cache : { // cached data
2535 viewport: null,
2536 containerOffset: null,
2537 areaWidth: 100 // available area width
2538 },
2539 nbSelected : 0, // number of selected items
2540 pagination : { currentPage: 0 }, // pagination data
2541 panThreshold: 60, // threshold value (in pixels) to block horizontal pan/swipe
2542 panYOnly: false, // threshold value reach -> definitively block horizontal pan until end of pan
2543 lastFullRow : -1, // number of the last row without holes
2544 lastDisplayedIdx: -1, // used to display the counter of not displayed items
2545 displayInterval : { from: 0, len: 0 },
2546 userEvents: null,
2547 hammertime: null,
2548 curNavLevel: 'l1', // current navigation level (l1 or LN)
2549 curWidth: 'me',
2550 albumSearch: '', // current search string -> title (used to filter the thumbnails on screen)
2551 albumSearchTags: '', // current search string -> tags
2552 lastZIndex: 0, // used to put a thumbnail on top of all others (for exemple for scale hover effect)
2553 lastRandomValue: 0,
2554 slider : { // slider on last thumbnail
2555 hostIdx: -1, // idx of the thumbnail hosting the slider
2556 hostItem: null, // item hosting the slider
2557 currentIdx: 0, // idx of the current displayed item
2558 nextIdx: 0, // idx of the next item to display in the slider
2559 timerID: 0,
2560 tween: null // tranistion tween instance
2561 },
2562 NGY2Item: function( idx ) { // returns a NGY2Item or null if it does not exist
2563 if( G.GOM.items[idx] == undefined || G.GOM.items[idx] == null ) { return null; }
2564 var i = G.GOM.items[idx].thumbnailIdx;
2565 return G.I[i];
2566 },
2567 // One GOM item (thumbnail)
2568 // function GTn(index, width, height) {
2569 GTn: function(index, width, height) {
2570 this.thumbnailIdx = index;
2571 this.width = 0; // thumbnail width
2572 this.height = 0; // thumbnail height
2573 this.top = 0; // position: top
2574 this.left = 0; // position: left
2575 this.row = 0; // position: row number
2576 this.imageWidth = width; // image width
2577 this.imageHeight = height; // image height
2578 this.resizedContentWidth = 0;
2579 this.resizedContentHeight = 0;
2580 this.displayed = false;
2581 this.neverDisplayed = true;
2582 this.inDisplayArea = false;
2583 },
2584 // Position the top of the gallery to make it visible, if not displayed
2585 ScrollToTop: function() {
2586
2587 if( G.GOM.firstDisplay ) {
2588 // do no scroll to top on first display
2589 return;
2590 }
2591
2592 if( G.$E.scrollableParent === null && !topInViewportVert(G.$E.base, 20) ) {
2593 // $('html, body').animate({scrollTop: G.$E.base.offset().top}, 200);
2594 G.$E.base.get(0).scrollIntoView();
2595 }
2596
2597 if( G.$E.scrollableParent !== null ) {
2598 // gallery in a scrollable container: check if we have to scroll up so that the top of the gallery is visible
2599 // vertical position of the scrollbar
2600 var scrollTop = G.$E.scrollableParent.scrollTop();
2601 // top of the gallery relative to the top of the scrollable container
2602 var dist = Math.abs(G.$E.scrollableParent.offset().top - G.$E.base.offset().top - scrollTop);
2603 if( scrollTop > dist ) {
2604 window.ng_draf( function() {
2605 // we need a little delay before setting the new scrollbar (but why?....)
2606 G.$E.scrollableParent.scrollTop(dist);
2607 });
2608 }
2609 }
2610 }
2611 };
2612
2613
2614 //------------------------
2615 //--- Viewer Object Model
2616
2617 G.VOM = {
2618 viewerDisplayed: false, // is the viewer currently displayed
2619 viewerIsFullscreen: false, // viewer in fullscreen mode
2620 infoDisplayed: false, // is the info box displayed
2621 toolbarsDisplayed: true, // the toolbars are displayed
2622 toolsHide: null,
2623 zoom : {
2624 posX: 0, // position to center zoom in/out
2625 posY: 0,
2626 userFactor: 1, // user zoom factor (applied to the baseZoom factor)
2627 isZooming: false
2628 },
2629 padding: { H: 0, V: 0 }, // padding for the image
2630 window: { lastWidth: 0, lastHeight: 0 },
2631 $viewer: null,
2632 $toolbar: null, // viewerToolbar
2633 $toolbarTL: null, // viewer toolbar on top left
2634 $toolbarTR: null, // viewer toolbar on top right
2635
2636 toolbarMode: 'std', // current toolbar mode (standard, minimized)
2637 playSlideshow : false, // slide show mode status
2638 playSlideshowTimerID: 0, // slideshow mode time
2639 slideshowDelay: 3000, // slideshow mode - delay before next image
2640 albumID: -1,
2641 viewerMediaIsChanged: false, // media display is currently modified
2642 items: [], // current list of images to be managed by the viewer
2643
2644 panMode: 'off', // if panning, which element -> media, gallery, or zoom - if not -> off
2645
2646 $baseCont: null, // lightbox container
2647 $content: null, // pointer to the 3 media in the viewer
2648 content: {
2649 previous : {
2650 vIdx: -1,
2651 $media: null,
2652 NGY2Item: function() {
2653 return G.I[ G.VOM.items[G.VOM.content.previous.vIdx].ngy2ItemIdx ];
2654 }
2655 },
2656 current : {
2657 vIdx: -1,
2658 $media: null,
2659 NGY2Item: function() {
2660 return G.I[ G.VOM.items[G.VOM.content.current.vIdx].ngy2ItemIdx ];
2661 }
2662 },
2663 next : {
2664 vIdx: -1,
2665 $media: null,
2666 NGY2Item: function() {
2667 return G.I[ G.VOM.items[G.VOM.content.next.vIdx].ngy2ItemIdx ];
2668 }
2669 }
2670 },
2671 IdxNext: function() {
2672 var n = 0;
2673 // if( G.VOM.currItemIdx <= (G.VOM.items.length-1) ) {
2674 if( G.VOM.content.current.vIdx < (G.VOM.items.length-1) ) {
2675 n = G.VOM.content.current.vIdx + 1;
2676 }
2677 return n;
2678 },
2679 IdxPrevious: function() {
2680 var n = G.VOM.content.current.vIdx - 1;
2681 if( G.VOM.content.current.vIdx == 0 ) {
2682 n = G.VOM.items.length - 1;
2683 }
2684 return n;
2685 },
2686
2687 gallery: {
2688 $elt: null, // Base container
2689 $tmbCont: null, // Thumbnail container
2690 gwidth: 0, // thumbnail container width (all thumbnails)
2691 vwidth: 0, // visible width of the gallery (just for the visible thumbnails)
2692 oneTmbWidth: 0,
2693 firstDisplay: true,
2694 posX: 0,
2695 SetThumbnailActive() {
2696 if( G.O.viewerGallery == 'none' ) { return; }
2697 this.$tmbCont.children().removeClass('activeVThumbnail');
2698 this.$tmbCont.children().eq( G.VOM.content.current.vIdx ).addClass('activeVThumbnail');
2699 this.firstDisplay = false;
2700 },
2701 Resize: function() {
2702 if( G.O.viewerGallery == 'none' ) { return; }
2703
2704 if( !this.firstDisplay ) {
2705 var viewerW = G.VOM.$viewer.width();
2706
2707 // Center base element
2708 var maxTmb = Math.trunc(viewerW / this.oneTmbWidth); // max thumbnail that can be displayed
2709 this.vwidth = maxTmb * this.oneTmbWidth;
2710 this.$elt.css({ width: this.vwidth, left: (viewerW - this.vwidth)/2 });
2711
2712 // Set the position the thumbnails container (if there's no enough space for all thumbnails)
2713 if( G.VOM.items.length >= maxTmb ) {
2714 var tmbPos = this.oneTmbWidth * G.VOM.content.current.vIdx; // left position of the selected thumbnail
2715
2716 if( (tmbPos + this.posX) < this.vwidth ) {
2717 if( tmbPos + this.posX < 0 ) {
2718 this.posX = -tmbPos;
2719 }
2720 }
2721 else {
2722 if( tmbPos + this.posX >= this.vwidth ) {
2723 this.posX = this.vwidth - (tmbPos + this.oneTmbWidth)
2724 }
2725 }
2726 }
2727
2728 this.PanGallery(0);
2729 }
2730 else {
2731 // first display of the gallery -> opacity transition
2732 new NGTweenable().tween({
2733 from: { opacity: 0 },
2734 to: { opacity: 1 },
2735 easing: 'easeInOutSine',
2736 duration: 1000,
2737 step: function (state) {
2738 // G.VOM.gallery.$elt.css( state );
2739 },
2740 finish: function (state) {
2741 // G.VOM.gallery.$elt.css({ opacity: 1});
2742 }
2743 });
2744
2745 }
2746 },
2747 PanGallery: function( panX ){
2748
2749 // all thumbnails are visible -> center the base element
2750 if( this.gwidth < G.VOM.$viewer.width() ) { // this.oneTmbWidth
2751 this.posX = (G.VOM.$viewer.width() - this.gwidth) / 2;
2752 panX = 0; // block pan
2753 }
2754
2755 // if( this.posX > (this.vwidth - this.oneTmbWidth) ) {
2756 if( this.posX > (this.vwidth - this.oneTmbWidth) ) {
2757 // gallery is outside of the screen, right side
2758 this.posX = this.vwidth - this.oneTmbWidth;
2759 }
2760 if( (this.posX+this.gwidth) < this.oneTmbWidth ) {
2761 // gallery is outside of the screen, left side
2762 this.posX = -this.gwidth + this.oneTmbWidth;
2763 }
2764
2765 this.$tmbCont.css( G.CSStransformName , 'translateX(' + (this.posX + panX) + 'px)');
2766 },
2767 PanGalleryEnd: function( velocity ) { // velocity = pixels/millisecond
2768
2769 var d = velocity * 100; // distance
2770 new NGTweenable().tween({
2771 from: { pan: G.VOM.gallery.posX },
2772 to: { pan: G.VOM.gallery.posX + d },
2773 easing: 'easeOutQuad',
2774 duration: 500,
2775 step: function (state) {
2776 G.VOM.gallery.posX = state.pan;
2777 G.VOM.gallery.PanGallery( 0 );
2778 }
2779 });
2780 }
2781
2782 },
2783 userEvents: null, // user events management
2784 hammertime: null, // hammer.js manager
2785 swipePosX: 0, // current horizontal swip position
2786 panPosX: 0, // position for manual pan
2787 panPosY: 0,
2788 panThreshold: 60, // threshold value (in pixels) to block vertical pan
2789 panXOnly: false, // threshold value reach -> definitively block vertical pan until end of pan
2790 singletapTime: 0,
2791 viewerTheme: '',
2792 timeImgChanged: 0,
2793 ImageLoader: {
2794 // fires a callback when image size is know (during download)
2795 // inspired by ROB - http://stackoverflow.com/users/226507/rob
2796 maxChecks: 1000,
2797 list: [],
2798 intervalHandle : null,
2799
2800 loadImage : function (callback, ngitem) {
2801 if( ngitem.mediaKind != 'img' ) { return; } // ignore - only for images
2802 var img = new Image ();
2803 img.src = ngitem.responsiveURL();
2804 if (img.width && img.height) {
2805 callback (img.width, img.height, ngitem, 0);
2806 }
2807 else {
2808 var obj = {image: img, url: ngitem.responsiveURL(), ngitem: ngitem, callback: callback, checks: 1};
2809 var i;
2810 for (i=0; i < this.list.length; i++) {
2811 if (this.list[i] == null)
2812 break;
2813 }
2814 this.list[i] = obj;
2815 if (!this.intervalHandle)
2816 this.intervalHandle = setInterval(this.interval, 50);
2817 }
2818 },
2819
2820 // called by setInterval
2821 interval : function () {
2822 var count = 0;
2823 var list = G.VOM.ImageLoader.list, item;
2824 for (var i=0; i<list.length; i++) {
2825 item = list[i];
2826 if (item != null) {
2827 if (item.image.width && item.image.height) {
2828 G.VOM.ImageLoader.list[i] = null;
2829 item.callback (item.image.width, item.image.height, item.ngitem, item.checks);
2830 }
2831 else if (item.checks > G.VOM.ImageLoader.maxChecks) {
2832 G.VOM.ImageLoader.list[i] = null;
2833 item.callback (0, 0, item.ngitem, item.checks);
2834 }
2835 else {
2836 count++;
2837 item.checks++;
2838 }
2839 }
2840 }
2841 if (count == 0) {
2842 G.VOM.ImageLoader.list = [];
2843 clearInterval (G.VOM.ImageLoader.intervalHandle);
2844 delete G.VOM.ImageLoader.intervalHandle;
2845 }
2846 }
2847 }
2848 }
2849 // One VOM item (image)
2850 function VImg( index ) {
2851 this.$e = null;
2852 this.ngy2ItemIdx = index;
2853 this.mediaNumber = G.VOM.items.length + 1;
2854 this.posX = 0; // to center the element
2855 this.posY = 0;
2856 }
2857
2858
2859 //------------------------
2860 //--- popup
2861 G.popup = {
2862 isDisplayed: false,
2863 $elt: null,
2864 close: function() {
2865 if( this.$elt != null ) {
2866 var tweenable = new NGTweenable();
2867 tweenable.tween({
2868 from: { opacity:1 },
2869 to: { opacity:0 },
2870 attachment: { t: this },
2871 easing: 'easeInOutSine',
2872 duration: 100,
2873 step: function (state, att) {
2874 if( att.t.$elt != null ) {
2875 att.t.$elt.css('opacity',state.opacity);
2876 }
2877 },
2878 finish: function (state, att) {
2879 if( att.t.$elt != null ) {
2880 att.t.$elt.remove();
2881 att.t.$elt=null;
2882 }
2883 att.t.isDisplayed=false;
2884 }
2885 });
2886 }
2887 }
2888 }
2889
2890
2891 // Color schemes - Gallery
2892 // Gradient generator: https://www.grabient.com/
2893 G.galleryTheme_dark = {
2894 navigationBar : { background: 'none', borderTop: '', borderBottom: '', borderRight: '', borderLeft: '' },
2895 navigationBreadcrumb : { background: '#111', color: '#fff', colorHover: '#ccc', borderRadius: '4px' },
2896 navigationFilter : { color: '#ddd', background: '#111', colorSelected: '#fff', backgroundSelected: '#111', borderRadius: '4px' },
2897 navigationPagination : { background: '#111', color: '#fff', colorHover: '#ccc', borderRadius: '4px' },
2898 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' },
2899 thumbnailIcon : { padding: '5px', color: '#fff', shadow:'' },
2900 pagination : { background: '#181818', backgroundSelected: '#666', color: '#fff', borderRadius: '2px', shapeBorder: '3px solid #666', shapeColor: '#444', shapeSelectedColor: '#aaa'}
2901 };
2902
2903 G.galleryTheme_light = {
2904 navigationBar : { background: 'none', borderTop: '', borderBottom: '', borderRight: '', borderLeft: '' },
2905 navigationBreadcrumb : { background: '#eee', color: '#000', colorHover: '#333', borderRadius: '4px' },
2906 navigationFilter : { background: '#eee', color: '#222', colorSelected: '#000', backgroundSelected: '#eee', borderRadius: '4px' },
2907 navigationPagination : { background: '#eee', color: '#000', colorHover: '#333', borderRadius: '4px' },
2908 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' },
2909 thumbnailIcon : { padding: '5px', color: '#fff' },
2910 pagination : { background: '#eee', backgroundSelected: '#aaa', color: '#000', borderRadius: '2px', shapeBorder: '3px solid #666', shapeColor: '#444', shapeSelectedColor: '#aaa'}
2911 };
2912
2913 // Color schemes - lightbox
2914 G.viewerTheme_dark = {
2915 background: '#000',
2916 barBackground: 'rgba(4, 4, 4, 0.2)',
2917 barBorder: '0px solid #111',
2918 barColor: '#fff',
2919 barDescriptionColor: '#ccc'
2920 };
2921 G.viewerTheme_light = {
2922 background: '#f8f8f8',
2923 barBackground: 'rgba(4, 4, 4, 0.7)',
2924 barBorder: '0px solid #111',
2925 barColor: '#fff',
2926 barDescriptionColor: '#ccc'
2927 };
2928
2929
2930
2931 // shortcut with G context to NGY2TOOLS
2932 // var NanoAlert = NGY2Tools.NanoAlert.bind(G);
2933 // var NanoConsoleLog = NGY2Tools.NanoConsoleLog.bind(G);
2934 var NanoAlert = NGY2Tools.NanoAlert;
2935 var NanoConsoleLog = NGY2Tools.NanoConsoleLog;
2936
2937
2938 /** @function initiateGallery2 */
2939 this.initiateGallery2 = function( element, params ) {
2940
2941 // GLOBAL OPTIONS
2942 G.O = params;
2943 // Base element
2944 G.$E.base = jQuery(element);
2945 G.baseEltID = G.$E.base.attr('id');
2946 if( G.baseEltID == undefined ) {
2947 // set a default ID to the root container
2948 var base_id = 'my_nanogallery';
2949 var c = '';
2950 var f = true;
2951 while( f ) {
2952 if (document.getElementById(base_id + c)) {
2953 // ID already exists
2954 if( c == '' ) {
2955 c = 1;
2956 }
2957 else {
2958 c++;
2959 }
2960 }
2961 else {
2962 f = false;
2963 G.baseEltID = 'my_nanogallery' + c;
2964 }
2965 }
2966 G.$E.base.attr('id', G.baseEltID)
2967 }
2968 G.O.$markup = [];
2969
2970 DefineVariables();
2971 SetPolyFills();
2972 BuildSkeleton();
2973 G.GOM.firstDisplayTime = Date.now();
2974
2975 SetGlobalEvents();
2976
2977 // check if only one specific album will be used
2978 if( !G.O.lightboxStandalone ) {
2979 var albumToDisplay = G.O.album;
2980 if( albumToDisplay == '' && G.O.photoset != '' ) {
2981 albumToDisplay = G.O.photoset;
2982 G.O.album = G.O.photoset;
2983 }
2984 if( albumToDisplay != '' ) {
2985 G.O.displayBreadcrumb = false; // no breadcrumb since only 1 album
2986 if( albumToDisplay.toUpperCase() != 'NONE' ) {
2987 // open a public album
2988 if( G.O.kind == "nano_photos_provider2") {
2989 if( albumToDisplay == decodeURIComponent(albumToDisplay)) {
2990 // album ID must be encoded
2991 albumToDisplay = encodeURIComponent(albumToDisplay);
2992 G.O.album = albumToDisplay;
2993 }
2994 }
2995 NGY2Item.New( G, '', '', albumToDisplay, '-1', 'album' );
2996 if( !ProcessLocationHash() ) {
2997 DisplayAlbum('-1', albumToDisplay);
2998 }
2999 return;
3000 }
3001 }
3002 }
3003
3004 // use full content
3005 // add base album
3006 NGY2Item.New( G, G.i18nTranslations.breadcrumbHome, '', '0', '-1', 'album' );
3007
3008 processStartOptions();
3009
3010 }
3011
3012
3013 /** @function processStartOptions */
3014 function processStartOptions() {
3015 // open image or album
3016 // 1. load hidden albums
3017 // 2. check if location hash set (deep linking)
3018 // 3. check openOnStart parameter
3019 // 4. open root album (ID=-1)
3020
3021 // hidden/private albums are loaded on plugin start (Picasa) --> no more available in Google Photos
3022 // if( G.albumListHidden.length > 0 ) {
3023 // jQuery.nanogallery2['data_'+G.O.kind](G, 'GetHiddenAlbums', G.albumListHidden, processStartOptionsPart2);
3024 // return;
3025 //}
3026
3027 if( !ProcessLocationHash() ) {
3028 processStartOptionsPart2();
3029 }
3030 }
3031
3032 /** @function processStartOptionsPart2 */
3033 function processStartOptionsPart2() {
3034
3035 // Check location hash + start parameters -> determine what to do on start
3036 if( G.O.lightboxStandalone ) {
3037 LightboxStandaloneFindContent();
3038 }
3039 else {
3040 // openOnStart parameter
3041 if( G.O.openOnStart != '' ) {
3042 var IDs = parseIDs(G.O.openOnStart);
3043 if( IDs.imageID != '0' ) {
3044 DisplayPhoto(IDs.imageID, IDs.albumID);
3045 }
3046 else {
3047 DisplayAlbum('-1', IDs.albumID);
3048 }
3049 }
3050 else {
3051 // open root album (ID = -1)
3052 DisplayAlbum('-1', 0);
3053 }
3054 }
3055 }
3056
3057
3058 // Lightbox standaone -> retrieve the items to display
3059 // Each item needs at least a thumbnail image and a big image
3060 // ONLY IMAGES SUPPORTED
3061 function LightboxStandaloneFindContent() {
3062
3063 G.GOM.curNavLevel = 'l1';
3064
3065 if( G.O.items == null ) {
3066 // retrieve all element having "data-nanogallery2-lightbox" and from the same group if defined
3067 var elts = jQuery('[data-nanogallery2-Lightbox');
3068 // element group
3069 var g = G.$E.base[0].dataset.nanogallery2Lgroup;
3070
3071 GetContentMarkup( elts, g );
3072 }
3073 else {
3074 // Content defined in the starting parameters
3075 GetContentApiObject();
3076 }
3077
3078 LightboxStandaloneDisplay();
3079
3080 }
3081
3082
3083 // Populate G.VOM.items + open the lightbox
3084 function LightboxStandaloneDisplay() {
3085
3086 G.VOM.items = [];
3087 G.VOM.albumID = '0';
3088 G.GOM.curNavLevel = 'l1';
3089 var vcnt = 0;
3090
3091 var srct = G.$E.base[0].src;
3092 var displayIdx = undefined;
3093 for( var idx = 0; idx < G.I.length; idx++ ) {
3094 if( G.I[idx].kind == 'image' ) {
3095 var vimg = new VImg(idx);
3096 G.VOM.items.push(vimg);
3097
3098 if( G.I[idx].thumbImg().src == srct ) {
3099 // same thumbnail URL
3100 displayIdx = vcnt;
3101 }
3102 vcnt++;
3103 }
3104
3105 }
3106 if( G.VOM.items.length > 0 ) {
3107 LightboxOpen( displayIdx );
3108 }
3109 else {
3110 NanoConsoleLog(G, 'No content for Lightbox standalone.');
3111 }
3112 }
3113
3114
3115
3116 // Parse string to extract albumID and imageID (format albumID/imageID)
3117 function parseIDs( IDs ) {
3118 var r = { albumID: '0', imageID: '0' };
3119
3120 var t = IDs.split('/');
3121 if( t.length > 0 ) {
3122 r.albumID = t[0];
3123 if( t.length > 1 ) {
3124 r.imageID = t[1];
3125 }
3126 }
3127 return r;
3128 }
3129
3130
3131 /** @function DisplayAlbum */
3132 function DisplayAlbum( imageID, albumID ) {
3133 // close viewer if already displayed
3134 if( G.VOM.viewerDisplayed ) {
3135 LightboxClose(null);
3136 }
3137
3138 // set current navigation level (l1 or lN)
3139 var albumIdx = NGY2Item.GetIdx(G, albumID);
3140 G.GOM.curNavLevel = 'lN';
3141 if( albumIdx == 0 ) {
3142 G.GOM.curNavLevel = 'l1';
3143 }
3144 G.layout.SetEngine();
3145 G.galleryResizeEventEnabled = false;
3146
3147 if( albumIdx == -1 ) {
3148 NGY2Item.New( G, '', '', albumID, '0', 'album' ); // create empty album
3149 albumIdx = G.I.length - 1;
3150 }
3151
3152 if( !G.I[albumIdx].contentIsLoaded ) {
3153 // get content of the album if not already loaded
3154 AlbumGetContent( albumID, DisplayAlbum, imageID, albumID );
3155 return;
3156 }
3157
3158 ThumbnailSelectionClear();
3159
3160 G.GOM.pagination.currentPage = 0;
3161 SetLocationHash( albumID, '' );
3162 GalleryRender( albumIdx );
3163
3164 }
3165
3166
3167 //----- manage the bottom area of the gallery -> "pagination" or "more button"
3168 function GalleryBottomManage() {
3169
3170 switch( G.galleryDisplayMode.Get() ) {
3171 case 'PAGINATION':
3172 if( G.layout.support.rows && G.galleryMaxRows.Get() > 0 ) {
3173 ManagePagination();
3174 }
3175 break;
3176 case 'MOREBUTTON':
3177 G.$E.conTnBottom.off('click');
3178 var nb = G.GOM.items.length-G.GOM.itemsDisplayed;
3179 if( nb == 0 ) {
3180 G.$E.conTnBottom.empty();
3181 }
3182 else {
3183 G.$E.conTnBottom.html('<div class="nGY2GalleryMoreButton"><div class="nGY2GalleryMoreButtonAnnotation">+'+nb+' ' + G.O.icons.galleryMoreButton +'</div></div>');
3184 G.$E.conTnBottom.on('click', function(e) {
3185 G.GOM.displayedMoreSteps++;
3186 GalleryResize();
3187 });
3188 }
3189 break;
3190 case 'FULLCONTENT':
3191 default:
3192 break;
3193 }
3194 }
3195
3196
3197 // add one album/folder to the breadcrumb
3198 function breadcrumbAdd( albumIdx ) {
3199
3200 var ic='';
3201 if( !G.O.breadcrumbHideIcons ) {
3202 ic=G.O.icons.breadcrumbAlbum;
3203 if( albumIdx == 0 ) {
3204 ic=G.O.icons.breadcrumbHome;
3205 }
3206 }
3207 var $newDiv =jQuery('<div class="oneItem">'+ic + G.I[albumIdx].title+'</div>').appendTo(G.GOM.navigationBar.$newContent.find('.nGY2Breadcrumb'));
3208 if( G.O.breadcrumbOnlyCurrentLevel ) {
3209 // link to parent folder (only 1 level is displayed in the breadcrumb)
3210 if( albumIdx == 0 ) {
3211 // no parent level -> stay on current one
3212 jQuery($newDiv).data('albumID','0');
3213 }
3214 else {
3215 jQuery($newDiv).data('albumID',G.I[albumIdx].albumID);
3216 }
3217 }
3218 else {
3219 // link to current folder
3220 jQuery($newDiv).data('albumID',G.I[albumIdx].GetID());
3221 }
3222 $newDiv.click(function() {
3223 var cAlbumID = jQuery(this).data('albumID');
3224 DisplayAlbum('-1', cAlbumID);
3225 return;
3226 });
3227 }
3228
3229 // add one separator to breadcrumb
3230 function breadcrumbAddSeparator( lastAlbumID ) {
3231 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'));
3232 jQuery($newSep).data('albumIdx',lastAlbumID);
3233 $newSep.click(function() {
3234 var sepAlbumIdx=jQuery(this).data('albumIdx');
3235 DisplayAlbum('-1', G.I[sepAlbumIdx].GetID());
3236 return;
3237 });
3238 }
3239
3240
3241
3242 // Manage the gallery toolbar (breadcrumb + tag filter + pagination next/previous)
3243 function GalleryNavigationBar( albumIdx ) {
3244
3245 // Title + background image
3246 // var bgImage='';
3247 // var l=G.I.length;
3248 // var albumID = G.I[albumIdx].GetID();
3249 // for( var idx=0; idx<l ; idx++) {
3250 // var item=G.I[idx];
3251 // if( item.kind == 'image' && item.isToDisplay(albumID) ) {
3252 // 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>';
3253 // break;
3254 // }
3255 // }
3256
3257 //console.log(bgImage);
3258
3259 // new navigation bar items are not build in the DOM, but in memory
3260 G.GOM.navigationBar.$newContent=jQuery('<div class="nGY2Navigationbar"></div>');
3261 //G.GOM.navigationBar.$newContent = jQuery(bgImage );
3262 //console.log(G.GOM.navigationBar.$newContent);
3263
3264 //-- manage breadcrumb
3265 if( G.O.displayBreadcrumb == true && !G.O.thumbnailAlbumDisplayImage) {
3266 // retrieve new folder level
3267 var newLevel = 0,
3268 lstItems=[];
3269 if( albumIdx != 0 ) {
3270 var l=G.I.length,
3271 parentID=0;
3272
3273 lstItems.push(albumIdx);
3274 var curIdx=albumIdx;
3275 newLevel++;
3276
3277 while( G.I[curIdx].albumID != 0 && G.I[curIdx].albumID != -1) {
3278 for(var i=1; i < l; i++ ) {
3279 if( G.I[i].GetID() == G.I[curIdx].albumID ) {
3280 curIdx=i;
3281 lstItems.push(curIdx);
3282 newLevel++;
3283 break;
3284 }
3285 }
3286 }
3287 }
3288
3289 // build breadcrumb
3290 if( !(G.O.breadcrumbAutoHideTopLevel && newLevel == 0) ) {
3291 BreadcrumbBuild( lstItems );
3292 }
3293 }
3294
3295
3296 //-- manage and build tag filters
3297 if( G.galleryFilterTags.Get() != false ) {
3298 var nTags = G.I[albumIdx].albumTagList.length;
3299 if( nTags > 0 ) {
3300 for(var i = 0; i < nTags; i++ ) {
3301 var s = G.I[albumIdx].albumTagList[i];
3302 var ic = G.O.icons.navigationFilterUnselected;
3303 var tagClass = 'Unselected';
3304 if( jQuery.inArray(s, G.I[albumIdx].albumTagListSel) >= 0 ) {
3305 tagClass = 'Selected';
3306 ic = G.O.icons.navigationFilterSelected;
3307 }
3308
3309 var $newTag = jQuery('<div class="nGY2NavigationbarItem nGY2NavFilter' + tagClass + '">'+ ic +' '+ s +'</div>').appendTo(G.GOM.navigationBar.$newContent);
3310
3311 $newTag.click(function() {
3312
3313 var $this = jQuery(this);
3314 var tag = $this.text().replace(/^\s*|\s*$/, ''); //trim trailing/leading whitespace
3315
3316 if( G.galleryFilterTagsMode.Get() == 'single' ) {
3317 // single TAG selection
3318 G.I[albumIdx].albumTagListSel = [];
3319 G.I[albumIdx].albumTagListSel.push(tag);
3320 }
3321 else {
3322 // multiple selection of TAGS
3323 // if( $this.hasClass('oneTagUnselected') ){
3324 if( $this.hasClass('nGY2NavFilterUnselected') ){
3325 G.I[albumIdx].albumTagListSel.push(tag);
3326 }
3327 else {
3328 var tidx=jQuery.inArray(tag,G.I[albumIdx].albumTagListSel);
3329 if( tidx != -1 ) {
3330 G.I[albumIdx].albumTagListSel.splice(tidx,1);
3331 }
3332 }
3333 $this.toggleClass('nGY2NavFilters-oneTagUnselected nGY2NavFilters-oneTagSelected');
3334 }
3335
3336 DisplayAlbum('-1', G.I[albumIdx].GetID());
3337 });
3338 }
3339
3340 // clear/reset TAGS selection
3341 var $newClearFilter=jQuery('<div class="nGY2NavigationbarItem nGY2NavFilterSelectAll">'+ G.O.icons.navigationFilterSelectedAll +'</div>').appendTo(G.GOM.navigationBar.$newContent);
3342 $newClearFilter.click(function() {
3343 // var nTags = G.I[albumIdx].albumTagList.length;
3344 G.I[albumIdx].albumTagListSel = [];
3345 // for(var i = 0; i < nTags; i++ ) {
3346 // var s = G.I[albumIdx].albumTagList[i];
3347 // G.I[albumIdx].albumTagListSel.push(s);
3348 // }
3349 DisplayAlbum('-1', G.I[albumIdx].GetID());
3350 });
3351 }
3352 }
3353
3354 // --- Gallery pagination next/previous
3355 if( G.galleryDisplayMode.Get() == "PAGINATION" && G.O.galleryPaginationTopButtons ) {
3356 if( G.layout.support.rows && G.galleryMaxRows.Get() > 0 ) {
3357 // ManagePagination( G.GOM.albumIdx );
3358 var $newTagPrev = jQuery('<div class="nGY2NavigationbarItem nGY2NavPagination">'+G.O.icons.navigationPaginationPrevious+'</div>').appendTo(G.GOM.navigationBar.$newContent);
3359 $newTagPrev.click(function() {
3360 paginationPreviousPage();
3361 });
3362 var $newTagNext = jQuery('<div class="nGY2NavigationbarItem nGY2NavPagination">'+G.O.icons.navigationPaginationNext+'</div>').appendTo(G.GOM.navigationBar.$newContent);
3363 $newTagNext.click(function() {
3364 paginationNextPage();
3365 });
3366 }
3367 }
3368
3369 }
3370
3371 function BreadcrumbBuild(lstItems) {
3372
3373 // console.log(G.GOM.navigationBar.$newContent);
3374 jQuery('<div class="nGY2NavigationbarItem nGY2Breadcrumb"></div>').appendTo(G.GOM.navigationBar.$newContent);
3375 // console.log(G.GOM.navigationBar.$newContent);
3376
3377 if( G.O.breadcrumbOnlyCurrentLevel ) {
3378 // display only 1 separator and the current folder level
3379 if( lstItems.length == 0 ) {
3380 breadcrumbAdd(0);
3381 }
3382 else {
3383 // var last=lstItems.length-1;
3384 if( lstItems.length == 1 ) {
3385 breadcrumbAddSeparator(0); // root level
3386 }
3387 else {
3388 breadcrumbAddSeparator(lstItems[0]);
3389 }
3390 breadcrumbAdd(lstItems[0]);
3391 }
3392 }
3393 else {
3394 // display the full breadcrum (full folder levels including root level)
3395 breadcrumbAdd(0);
3396 if( lstItems.length > 0 ) {
3397 breadcrumbAddSeparator(0);
3398 for(var i=lstItems.length-1; i>=0 ; i-- ) {
3399 breadcrumbAdd(lstItems[i]);
3400 if( i > 0 ) {
3401 breadcrumbAddSeparator(lstItems[i-1]);
3402 }
3403 }
3404 }
3405 }
3406
3407 }
3408
3409
3410 // Display gallery pagination
3411 function ManagePagination() {
3412
3413 G.$E.conTnBottom.css('opacity', 0);
3414 G.$E.conTnBottom.children().remove();
3415
3416 if( G.GOM.items.length == 0 ) { return; } // no thumbnail to display
3417
3418 // calculate the number of pages
3419 var nbPages = Math.ceil((G.GOM.items[G.GOM.items.length - 1].row + 1)/G.galleryMaxRows.Get());
3420
3421 // only one page -> do not display pagination
3422 if( nbPages == 1 ) { return; }
3423
3424 // check if current page still exists (for example after a resize)
3425 if( G.GOM.pagination.currentPage > (nbPages-1) ) {
3426 G.GOM.pagination.currentPage = nbPages-1;
3427 }
3428
3429 GalleryRenderGetInterval();
3430 // nothing to display --> exit
3431 if( G.GOM.displayInterval.len == 0 ) { return; }
3432
3433 // display "previous"
3434 if( G.O.galleryPaginationMode == 'NUMBERS' && G.GOM.pagination.currentPage > 0 ) {
3435 var $eltPrev = jQuery('<div class="nGY2PaginationPrev">'+G.O.icons.paginationPrevious+'</div>').appendTo(G.$E.conTnBottom);
3436 $eltPrev.click(function(e) {
3437 paginationPreviousPage();
3438 });
3439 }
3440
3441 var firstPage = 0;
3442 var lastPage = nbPages;
3443 if( G.O.galleryPaginationMode != 'NUMBERS' ) {
3444 // no 'previous'/'next' and no max number of pagination items
3445 firstPage = 0;
3446 }
3447 else {
3448 // display pagination numbers and previous/next
3449 // var vp = G.O.paginationVisiblePages;
3450 var numberOfPagesToDisplay = G.O.paginationVisiblePages;
3451 if( numberOfPagesToDisplay >= nbPages ) {
3452 firstPage = 0;
3453 }
3454 else {
3455 // we have more pages than we want to display
3456 var nbBeforeAfter = 0;
3457 if( isOdd(numberOfPagesToDisplay) ) {
3458 nbBeforeAfter = (numberOfPagesToDisplay + 1) / 2;
3459 }
3460 else {
3461 nbBeforeAfter = numberOfPagesToDisplay / 2;
3462 }
3463
3464 if( G.GOM.pagination.currentPage < nbBeforeAfter ) {
3465 firstPage = 0;
3466 lastPage = numberOfPagesToDisplay - 1;
3467 if( lastPage > nbPages ) {
3468 lastPage = nbPages - 1;
3469 }
3470 }
3471 else {
3472 firstPage = G.GOM.pagination.currentPage - nbBeforeAfter;
3473 lastPage = firstPage + numberOfPagesToDisplay;
3474 if( lastPage > nbPages ) {
3475 lastPage = nbPages - 1;
3476 }
3477 }
3478
3479 if( (lastPage - firstPage) < numberOfPagesToDisplay ) {
3480 firstPage = lastPage - numberOfPagesToDisplay;
3481 if( firstPage < 0 ) {
3482 firstPage = 0;
3483 }
3484 }
3485
3486 }
3487 }
3488
3489 // render pagination items
3490 for(var i = firstPage; i < lastPage; i++ ) {
3491 var c = '';
3492 var p = '';
3493
3494 switch( G.O.galleryPaginationMode ) {
3495 case 'NUMBERS':
3496 c = 'nGY2paginationItem';
3497 p = i + 1;
3498 break;
3499 case 'DOTS':
3500 c = 'nGY2paginationDot';
3501 break;
3502 case 'RECTANGLES':
3503 c = 'nGY2paginationRectangle';
3504 break;
3505 }
3506 if( i == G.GOM.pagination.currentPage ) {
3507 c += 'CurrentPage';
3508 }
3509
3510 var elt$ = jQuery('<div class="' + c + '">' + p + '</div>').appendTo(G.$E.conTnBottom);
3511 elt$.data('pageNumber', i );
3512 elt$.click( function(e) {
3513 G.GOM.pagination.currentPage = jQuery(this).data('pageNumber');
3514 TriggerCustomEvent('pageChanged');
3515
3516 // scroll to top of gallery if not displayed
3517 G.GOM.ScrollToTop();
3518
3519 GalleryDisplayPart1();
3520 GalleryDisplayPart2( true );
3521 });
3522
3523 }
3524
3525 // display "next"
3526 if( G.O.galleryPaginationMode == 'NUMBERS' && (G.GOM.pagination.currentPage + 1) < nbPages ) {
3527 var $eltNext = jQuery('<div class="nGY2PaginationNext">' + G.O.icons.paginationNext + '</div>').appendTo(G.$E.conTnBottom);
3528 $eltNext.click( function(e) {
3529 paginationNextPage();
3530 });
3531 }
3532
3533 G.$E.conTnBottom.css('opacity', 1);
3534
3535 }
3536 function isOdd(num) { return (num % 2) == 1;}
3537
3538 // pagination - next page
3539 function paginationNextPage() {
3540 // var aIdx = G.GOM.albumIdx;
3541 var n1 = 0;
3542 ThumbnailHoverOutAll();
3543
3544 // pagination - max lines per page mode
3545 if( G.galleryMaxRows.Get() > 0 ) {
3546 // number of pages
3547 n1 = (G.GOM.items[G.GOM.items.length - 1].row + 1) / G.galleryMaxRows.Get();
3548 }
3549 var n2 = Math.ceil(n1);
3550 var pn = G.GOM.pagination.currentPage;
3551 if( pn < (n2-1) ) {
3552 pn++;
3553 }
3554 else {
3555 pn = 0;
3556 }
3557
3558 G.GOM.pagination.currentPage = pn;
3559 TriggerCustomEvent('pageChanged');
3560
3561 // scroll to top of gallery if not displayed
3562 G.GOM.ScrollToTop();
3563
3564 GalleryDisplayPart1();
3565 GalleryDisplayPart2( true );
3566 }
3567
3568 // pagination - previous page
3569 function paginationPreviousPage() {
3570 // var aIdx=G.$E.conTnBottom.data('galleryIdx'),
3571 // var aIdx = G.GOM.albumIdx;
3572 var n1 = 0;
3573
3574 ThumbnailHoverOutAll();
3575
3576 // pagination - max lines per page mode
3577 if( G.galleryMaxRows.Get() > 0 ) {
3578 // number of pages
3579 n1 = (G.GOM.items[G.GOM.items.length - 1].row + 1) / G.galleryMaxRows.Get();
3580 }
3581 var n2 = Math.ceil(n1);
3582
3583 // var pn=G.$E.conTnBottom.data('currentPageNumber');
3584 var pn = G.GOM.pagination.currentPage;
3585 if( pn > 0 ) {
3586 pn--;
3587 }
3588 else {
3589 pn = n2 - 1;
3590 }
3591
3592 G.GOM.pagination.currentPage = pn;
3593 TriggerCustomEvent('pageChanged');
3594
3595 // scroll to top of gallery if not displayed
3596 G.GOM.ScrollToTop();
3597
3598 GalleryDisplayPart1();
3599 GalleryDisplayPart2( true );
3600 }
3601
3602 // retrieve the from/to intervall for gallery thumbnail render
3603 function GalleryRenderGetInterval() {
3604 G.GOM.displayInterval.from = 0;
3605 G.GOM.displayInterval.len = G.I.length;
3606
3607 switch( G.galleryDisplayMode.Get() ) {
3608 case 'PAGINATION':
3609 if( G.layout.support.rows ) {
3610 let nbTn = G.GOM.items.length;
3611 var firstRow = G.GOM.pagination.currentPage * G.galleryMaxRows.Get();
3612 var lastRow = firstRow + G.galleryMaxRows.Get();
3613 var firstTn = -1;
3614 G.GOM.displayInterval.len = 0;
3615 for( var i = 0; i < nbTn ; i++ ) {
3616 let curTn = G.GOM.items[i];
3617 if( curTn.row >= firstRow && curTn.row < lastRow ) {
3618 if( firstTn == -1 ) {
3619 G.GOM.displayInterval.from = i;
3620 firstTn = i;
3621 }
3622 G.GOM.displayInterval.len++;
3623 }
3624 }
3625 }
3626 break;
3627 case 'MOREBUTTON':
3628 if( G.layout.support.rows ) {
3629 let nbTn = G.GOM.items.length;
3630 let lastRow = G.O.galleryDisplayMoreStep * (G.GOM.displayedMoreSteps+1);
3631 G.GOM.displayInterval.len = 0;
3632 for( var i = 0; i < nbTn ; i++ ) {
3633 let curTn = G.GOM.items[i];
3634 if( curTn.row < lastRow ) {
3635 G.GOM.displayInterval.len++;
3636 }
3637 }
3638 }
3639 break;
3640 case 'ROWS':
3641 if( G.layout.support.rows ) {
3642 let nbTn = G.GOM.items.length;
3643 let lastRow = G.galleryMaxRows.Get();
3644 if( G.galleryLastRowFull.Get() && G.GOM.lastFullRow != -1 ) {
3645 if( lastRow > (G.GOM.lastFullRow + 1) ) {
3646 lastRow = G.GOM.lastFullRow + 1;
3647 }
3648 }
3649 G.GOM.displayInterval.len = 0;
3650 for( var i = 0; i < nbTn ; i++ ) {
3651 let curTn = G.GOM.items[i];
3652 if( curTn.row < lastRow ) {
3653 G.GOM.displayInterval.len++;
3654 }
3655 }
3656 }
3657 break;
3658 default:
3659 case 'FULLCONTENT':
3660 if( G.layout.support.rows && G.galleryLastRowFull.Get() && G.GOM.lastFullRow != -1 ) {
3661 let nbTn = G.GOM.items.length;
3662 let lastRow = G.GOM.lastFullRow + 1;
3663 G.GOM.displayInterval.len = 0;
3664 for( var i = 0; i < nbTn ; i++ ) {
3665 let curTn = G.GOM.items[i];
3666 if( curTn.row < lastRow ) {
3667 G.GOM.displayInterval.len++;
3668 }
3669 }
3670 }
3671 break;
3672 }
3673 }
3674
3675
3676 // RENDER THE GALLERY
3677 function GalleryRender( albumIdx ) {
3678 TriggerCustomEvent('galleryRenderStart');
3679 clearTimeout(G.GOM.slider.timerID);
3680 G.GOM.slider.hostIdx = -1; // disabled slider on thumbnail
3681
3682 var fu=G.O.fnGalleryRenderStart;
3683 if( fu !== null ) {
3684 // typeof fu == 'function' ? fu(albumIdx) : window[fu](albumIdx);
3685 typeof fu == 'function' ? fu( G.I[G.GOM.albumIdx] ) : window[fu]( G.I[G.GOM.albumIdx] );
3686 }
3687
3688 G.layout.SetEngine();
3689 G.galleryResizeEventEnabled = false;
3690 G.GOM.albumIdx = -1;
3691 G.GOM.lastDisplayedIdx = -1;
3692
3693 // pagination
3694 if( G.$E.conTnBottom !== undefined ) {
3695 // G.$E.conTnBottom.children().remove();
3696 G.$E.conTnBottom.empty();
3697 }
3698
3699 // navigation toolbar (breadcrumb + tag filters + pagination next/previous)
3700 GalleryNavigationBar(albumIdx);
3701
3702 if( G.GOM.firstDisplay ) {
3703 // first gallery display
3704 G.GOM.firstDisplay = false;
3705 var d = Date.now()-G.GOM.firstDisplayTime;
3706 if( d < G.O.galleryRenderDelay ) {
3707 // display after defined delay
3708 // setTimeout( function() { GalleryRenderPart1( albumIdx )}, G.O.galleryRenderDelay-d );
3709 requestTimeout( function() { GalleryRenderPart1( albumIdx )}, G.O.galleryRenderDelay-d );
3710 }
3711 else {
3712 GalleryRenderPart1( albumIdx );
3713 }
3714 G.O.galleryRenderDelay = 0;
3715
3716 }
3717 else {
3718 var hideNavigationBar = false;
3719 if( G.GOM.navigationBar.$newContent.children().length == 0 ) {
3720 hideNavigationBar = true;
3721 }
3722
3723 // hide everything
3724 var tweenable = new NGTweenable();
3725 tweenable.tween({
3726 from: { 'opacity': 1 },
3727 to: { 'opacity': 0 },
3728 duration: 300,
3729 easing: 'easeInQuart',
3730 attachment: { h: hideNavigationBar },
3731 step: function (state, att) {
3732 G.$E.conTnParent.css({'opacity': state.opacity });
3733 if( att.h ) {
3734 G.$E.conNavigationBar.css({ 'opacity': state.opacity });
3735 }
3736 },
3737 finish: function (state, att) {
3738 if( att.h ) {
3739 G.$E.conNavigationBar.css({ 'opacity': 0, 'display': 'none' });
3740 }
3741 // scroll to top of the gallery if needed
3742
3743 G.GOM.ScrollToTop();
3744
3745 GalleryRenderPart1( albumIdx );
3746 }
3747 });
3748 }
3749 }
3750
3751
3752 function GalleryRenderPart1( albumIdx ) {
3753 // display new navigation bar
3754 var oldN = G.$E.conNavigationBar.children().length;
3755 G.$E.conNavigationBar.empty();
3756 G.GOM.navigationBar.$newContent.children().clone(true,true).appendTo(G.$E.conNavigationBar);
3757 // G.GOM.navigationBar.$newContent.appendTo(G.$E.conNavigationBar);
3758 if( G.$E.conNavigationBar.children().length > 0 && oldN == 0 ) {
3759 G.$E.conNavigationBar.css({ 'opacity': 0, 'display': 'block' });
3760 var tweenable = new NGTweenable();
3761 tweenable.tween({
3762 from: { opacity: 0 },
3763 to: { opacity: 1 },
3764 duration: 200,
3765 easing: 'easeInQuart',
3766 step: function (state) {
3767 // window.ng_draf( function() {
3768 G.$E.conNavigationBar.css( state );
3769 // });
3770 },
3771 finish: function (state) {
3772 // window.ng_draf( function() {
3773 G.$E.conNavigationBar.css({ 'opacity': 1 });
3774 // display gallery
3775 // GalleryRenderPart2( albumIdx );
3776 // setTimeout(function(){ GalleryRenderPart2(albumIdx) }, 60);
3777 requestTimeout(function(){ GalleryRenderPart2(albumIdx) }, 60);
3778 // });
3779 }
3780 });
3781 }
3782 else {
3783 requestTimeout(function(){ GalleryRenderPart2(albumIdx) }, 60);
3784 }
3785
3786 }
3787
3788 // Gallery render part 2 -> remove all existing thumbnails
3789 function GalleryRenderPart2(albumIdx) {
3790
3791 G.GOM.lastZIndex = parseInt(G.$E.base.css('z-index'));
3792 if( isNaN(G.GOM.lastZIndex) ) {
3793 G.GOM.lastZIndex=0;
3794 }
3795 G.$E.conTnParent.css({ 'opacity': 0 });
3796 G.$E.conTn.off().empty();
3797 var l = G.I.length;
3798 for( var i = 0; i < l ; i++ ) {
3799 // reset each item
3800 var item = G.I[i];
3801 item.hovered = false;
3802 item.$elt = null;
3803 item.$Elts = [];
3804 item.eltTransform = [];
3805 item.eltFilter = [];
3806 item.width = 0;
3807 item.height = 0;
3808 item.left = 0;
3809 item.top = 0;
3810 item.resizedContentWidth = 0;
3811 item.resizedContentHeight = 0;
3812 item.thumbnailImgRevealed = false;
3813 }
3814
3815 if( G.CSStransformName == null ) {
3816 G.$E.conTn.css('left', '0px');
3817 }
3818 else {
3819 // G.$E.conTn.css( G.CSStransformName, 'translateX(0px)');
3820 G.$E.conTn.css( G.CSStransformName, 'none');
3821 }
3822
3823 // setTimeout(function(){ GalleryRenderPart3(albumIdx) }, 60);
3824 requestTimeout(function(){ GalleryRenderPart3(albumIdx) }, 60);
3825 // GalleryRenderPart3(albumIdx);
3826
3827 }
3828
3829 // Gallery render part 3 -> start building the new gallery
3830 function GalleryRenderPart3(albumIdx) {
3831 var d = new Date();
3832
3833 G.$E.conTnParent.css( 'opacity', 1);
3834
3835 G.GOM.items = [];
3836 G.GOM.displayedMoreSteps = 0;
3837 // retrieve label height
3838 if( G.O.thumbnailLabel.get('position') == 'onBottom' ) {
3839 // retrieve height each time because size can change depending on thumbnail's settings
3840 G.tn.labelHeight[G.GOM.curNavLevel] = ThumbnailGetLabelHeight();
3841 }
3842 else {
3843 G.tn.labelHeight[G.GOM.curNavLevel] = 0;
3844 }
3845 G.GOM.albumIdx=albumIdx;
3846
3847 TriggerCustomEvent('galleryRenderEnd');
3848 var fu=G.O.fnGalleryRenderEnd;
3849 if( fu !== null ) {
3850 // typeof fu == 'function' ? fu(albumIdx) : window[fu](albumIdx);
3851 typeof fu == 'function' ? fu(G.I[G.GOM.albumIdx] ) : window[fu](G.I[G.GOM.albumIdx] );
3852 }
3853
3854 // Step 1: populate GOM
3855 if( GalleryPopulateGOM() ) {
3856 // step 2: calculate layout
3857 GallerySetLayout();
3858
3859 // step 3: display whole gallery
3860 GalleryAppear();
3861
3862 // step 4: display thumbnails
3863 GalleryDisplayPart1();
3864 requestTimeout(function(){ GalleryDisplayPart2( false ) }, 120);
3865 }
3866 else {
3867 G.galleryResizeEventEnabled = true;
3868 }
3869
3870 if( G.O.debugMode ) { console.log('GalleryRenderPart3: '+ (new Date()-d)); }
3871
3872 }
3873
3874
3875 // Resize the gallery
3876 function GalleryResize() {
3877 var d = new Date();
3878 G.galleryResizeEventEnabled = false;
3879 // G.GOM.cache.areaWidth=G.$E.conTnParent.width();
3880 if( GallerySetLayout() == false ) {
3881 G.galleryResizeEventEnabled = true;
3882 if( G.O.debugMode ) { console.log('GalleryResize1: '+ (new Date()-d)); }
3883 return;
3884 }
3885 if( G.O.debugMode ) { console.log('GalleryResizeSetLayout: '+ (new Date()-d)); }
3886
3887 GalleryDisplayPart1();
3888 GalleryDisplayPart2( false );
3889
3890 if( G.O.debugMode ) { console.log('GalleryResizeFull: '+ (new Date()-d)); }
3891 }
3892
3893
3894
3895 // copy items (album content) to GOM
3896 // returns:
3897 // true: thumbnail image size is needed for the layout, but not set -> retrieve the sizes and display gallery
3898 function GalleryPopulateGOM() {
3899
3900 var preloadImages = '';
3901 var imageSizeRequested = false;
3902 var albumID = G.I[G.GOM.albumIdx].GetID();
3903 var l = G.I.length;
3904 var cnt = 0;
3905
3906 for( var idx = 0; idx < l; idx++ ) {
3907 var item = G.I[idx];
3908 // check album
3909 if( item.isToDisplay(albumID) ) {
3910 var w = item.thumbImg().width;
3911 var h = item.thumbImg().height;
3912 // if unknown image size and layout is not grid --> we need to retrieve the size of the images
3913 if( G.layout.prerequisite.imageSize && ( w == 0 || h == 0) ) {
3914 // if( true ) {
3915 imageSizeRequested = true;
3916 preloadImages += '<img src="'+item.thumbImg().src+'" data-idx="'+cnt+'" data-albumidx="'+G.GOM.albumIdx+'">';
3917 }
3918
3919 // set default size if required
3920 if( h == 0 ) {
3921 h = G.tn.defaultSize.getHeight();
3922 }
3923 if( w == 0 ) {
3924 w = G.tn.defaultSize.getWidth();
3925 }
3926 var tn = new G.GOM.GTn(idx, w, h);
3927 G.GOM.items.push(tn);
3928 cnt++;
3929 }
3930 }
3931
3932 TriggerCustomEvent('galleryObjectModelBuilt');
3933 var fu = G.O.fnGalleryObjectModelBuilt;
3934 if( fu !== null ) {
3935 typeof fu == 'function' ? fu() : window[fu]();
3936 }
3937
3938 if( imageSizeRequested ) {
3939 // preload images to retrieve their size and then resize the gallery (=GallerySetLayout()+ GalleryDisplay())
3940 var $newImg = jQuery(preloadImages);
3941 var gi_imgLoad = ngimagesLoaded( $newImg );
3942 $newImg = null;
3943 gi_imgLoad.on( 'progress', function( instance, image ) {
3944
3945 if( image.isLoaded ) {
3946 var idx = image.img.getAttribute('data-idx');
3947 var albumIdx = image.img.getAttribute('data-albumidx');
3948 if( albumIdx == G.GOM.albumIdx ) {
3949 // ignore event if not on current album
3950 var curTn = G.GOM.items[idx];
3951 curTn.imageWidth = image.img.naturalWidth;
3952 curTn.imageHeight = image.img.naturalHeight;
3953 var item = G.I[curTn.thumbnailIdx];
3954 item.thumbs.width[G.GOM.curNavLevel][G.GOM.curWidth] = curTn.imageWidth;
3955 item.thumbs.height[G.GOM.curNavLevel][G.GOM.curWidth] = curTn.imageHeight;
3956
3957 // resize the gallery
3958 G.GalleryResizeThrottled();
3959
3960 // set the retrieved size to all levels with same configuration
3961 var object = item.thumbs.width.l1;
3962 for (let property in object) {
3963 if (object.hasOwnProperty(property)) {
3964 if( property != G.GOM.curWidth ) {
3965 if( G.tn.settings.width.l1[property] == G.tn.settings.getW() && G.tn.settings.height.l1[property] == G.tn.settings.getH() ) {
3966 item.thumbs.width.l1[property] = curTn.imageWidth;
3967 item.thumbs.height.l1[property] = curTn.imageHeight;
3968 }
3969 }
3970 }
3971 }
3972 object = item.thumbs.width.lN;
3973 for (let property in object) {
3974 if (object.hasOwnProperty(property)) {
3975 if( property != G.GOM.curWidth ) {
3976 if( G.tn.settings.width.lN[property] == G.tn.settings.getW() && G.tn.settings.height.lN[property] == G.tn.settings.getH() ) {
3977 item.thumbs.width.lN[property] = curTn.imageWidth;
3978 item.thumbs.height.lN[property] = curTn.imageHeight;
3979 }
3980 }
3981 }
3982 }
3983 }
3984 }
3985 });
3986 G.galleryResizeEventEnabled = true;
3987 return false;
3988 }
3989 else {
3990 return true;
3991 }
3992
3993 }
3994
3995 //----- Calculate the layout of the thumbnails for the full gallery
3996 function GallerySetLayout() {
3997 var r = true;
3998 // width of the available area
3999 G.GOM.cache.areaWidth = G.$E.conTnParent.width();
4000 G.GOM.displayArea = { width:0, height:0 };
4001
4002 switch( G.layout.engine ) {
4003 case 'JUSTIFIED':
4004 r = GallerySetLayoutWidthtAuto();
4005 break;
4006 case 'CASCADING':
4007 r = GallerySetLayoutHeightAuto();
4008 break;
4009 case 'MOSAIC':
4010 r = GallerySetLayoutMosaic();
4011 break;
4012 case 'GRID':
4013 default:
4014 r = GallerySetLayoutGrid();
4015 break;
4016 }
4017
4018 TriggerCustomEvent('galleryLayoutApplied');
4019 var fu = G.O.fnGalleryLayoutApplied;
4020 if( fu !== null ) {
4021 typeof fu == 'function' ? fu() : window[fu]();
4022 }
4023 return r;
4024
4025 }
4026
4027
4028 //----- CASCADING LAYOUT
4029 function GallerySetLayoutHeightAuto() {
4030 var curCol = 0,
4031 areaWidth = G.GOM.cache.areaWidth,
4032 curRow = 0,
4033 colHeight = [],
4034 maxCol = NbThumbnailsPerRow(areaWidth),
4035 gutterWidth = 0,
4036 gutterHeight = G.tn.settings.GetResponsive('gutterHeight');
4037 var w = 0;
4038 var scaleFactor = 1;
4039 var tnWidth = G.tn.defaultSize.getOuterWidth();
4040 var nbTn = G.GOM.items.length;
4041 var curPosY = 0;
4042
4043 if( G.O.thumbnailAlignment == 'justified' ) {
4044 maxCol = Math.min(maxCol, nbTn);
4045 gutterWidth = ( maxCol == 1 ? 0 : (areaWidth - (maxCol * tnWidth) ) / (maxCol - 1) );
4046 }
4047 else {
4048 gutterWidth = G.tn.settings.GetResponsive('gutterWidth');
4049 }
4050
4051
4052 var borderWidth = G.tn.opt.Get('borderHorizontal') * 2;
4053 var borderHeight = G.tn.opt.Get('borderVertical') * 2;
4054
4055 G.GOM.lastFullRow=-1; // feature disabled
4056
4057 // Retrieve the real used width of the area (the evaluation is based on the content of the first line)
4058 if( G.O.thumbnailAlignment == 'fillWidth' ) {
4059 // fillWidth --> evaluate scale factor and number of columns
4060 var totalGutterWidth = (maxCol - 1) * gutterWidth;
4061 scaleFactor = (areaWidth - totalGutterWidth) / (maxCol * tnWidth);
4062 if( scaleFactor > 1 ) {
4063 maxCol++; // add one column and re-evaluate the scale factor
4064 }
4065 totalGutterWidth = (maxCol - 1) * gutterWidth;
4066 scaleFactor = Math.min( (areaWidth - totalGutterWidth) / (maxCol*tnWidth), 1); // no upscale
4067 }
4068
4069
4070 tnWidth = Math.round( tnWidth * scaleFactor);
4071 var contentWidth = tnWidth - borderWidth;
4072
4073 // loop to position the thumbnails, and set their size
4074 var baseHeight = Math.round( G.tn.opt.Get('baseGridHeight') * scaleFactor );
4075 for( var i = 0; i < nbTn ; i++ ) {
4076 var curTn = G.GOM.items[i];
4077 if( curTn.deleted == true ) { break; } // item is logically deleted
4078 if( curTn.imageHeight > 0 && curTn.imageWidth > 0 ) {
4079 var curPosX = 0,
4080 curPosY = 0;
4081 var imageRatio = curTn.imageHeight / curTn.imageWidth;
4082 // curTn.resizedContentWidth = tnWidth - borderWidth;
4083 curTn.resizedContentWidth = contentWidth;
4084 curTn.resizedContentHeight = curTn.resizedContentWidth * imageRatio;
4085 if( baseHeight > 0 ) {
4086 // grid based vertical position
4087 var t = Math.max( Math.trunc(curTn.resizedContentHeight/baseHeight), 1) ;
4088 curTn.resizedContentHeight = baseHeight * t + ((t-1)*(borderHeight+gutterHeight));
4089 }
4090
4091 curTn.height = curTn.resizedContentHeight + borderHeight + G.tn.labelHeight.get();
4092 curTn.width = tnWidth;
4093 curTn.row = 0;
4094
4095 if( curRow == 0 ) {
4096 // first row
4097 curPosX = curCol * (tnWidth + gutterWidth);
4098 colHeight[curCol] = curTn.height + gutterHeight;
4099
4100 curCol++;
4101 if( curCol >= maxCol ) {
4102 curCol = 0;
4103 curRow++;
4104 }
4105 }
4106 else {
4107 var c=0,
4108 minColHeight=colHeight[0];
4109 for( var j = 1; j < maxCol; j++) {
4110 if( (colHeight[j] + 5) < minColHeight ) { // +5 --> threshold
4111 minColHeight = colHeight[j];
4112 c = j;
4113 //break;
4114 }
4115 }
4116 curPosY = colHeight[c];
4117 curPosX = c * (tnWidth + gutterWidth);
4118 colHeight[c] = curPosY + curTn.height + gutterHeight;
4119 }
4120
4121 var x = curPosX;
4122 if( G.O.RTL) {
4123 x= w - curPosX - tnWidth;
4124 }
4125
4126 curTn.left = x;
4127 curTn.top = curPosY;
4128 }
4129 }
4130
4131 G.GOM.displayArea.width= maxCol * (tnWidth + gutterWidth) - gutterWidth;
4132 return true;
4133 }
4134
4135
4136 //----- JUSTIFIED LAYOUT
4137 function GallerySetLayoutWidthtAuto() {
4138 var curWidth = 0,
4139 areaWidth = G.GOM.cache.areaWidth,
4140 lastPosX = 0,
4141 curPosY = 0,
4142 rowLastItem = [],
4143 rowNum = 0,
4144 rowHeight = [],
4145 bNewRow = false,
4146 cnt = 0,
4147 gutterWidth = G.tn.settings.GetResponsive('gutterWidth'),
4148 gutterHeight = G.tn.settings.GetResponsive('gutterHeight');
4149 // by grief-of-these-days
4150 var maxRowHeightVertical = 0; // max height of a row with vertical thumbs
4151 var maxRowHeightHorizontal = 0; // max height of a row with horizontal thumbs
4152 var rowHasVertical = false; // current row has vertical thumbs
4153 var rowHasHorizontal = false; // current row has horizontal thumbs
4154
4155 var tnHeight = G.tn.defaultSize.getOuterHeight();
4156 var borderWidth = G.tn.opt.Get('borderHorizontal') * 2;
4157 var borderHeight = G.tn.opt.Get('borderVertical') * 2;
4158 var nbTnInCurrRow = 1;
4159 var nbTn = G.GOM.items.length;
4160
4161 // first loop --> retrieve each row image height
4162 for( var i = 0; i < nbTn ; i++ ) {
4163 let curTn = G.GOM.items[i];
4164 if( curTn.deleted == true ) { break; } // item is logically deleted
4165 if( curTn.imageWidth > 0 ) {
4166 let imageRatio = curTn.imageWidth / curTn.imageHeight;
4167 let imageWidth = Math.floor( tnHeight * imageRatio );
4168
4169 if( bNewRow ) {
4170 bNewRow = false;
4171 rowNum++;
4172 curWidth = 0;
4173 rowHasVertical = false;
4174 rowHasHorizontal = false;
4175 nbTnInCurrRow = 1;
4176 }
4177 // by grief-of-these-days
4178 if( curTn.imageHeight > curTn.imageWidth ) {
4179 rowHasVertical = true;
4180 }
4181 else {
4182 rowHasHorizontal = true;
4183 }
4184
4185 if( (curWidth + gutterWidth + imageWidth) < (areaWidth - (nbTnInCurrRow * borderWidth)) ) {
4186 // enough place left in the current row
4187 curWidth += imageWidth + gutterWidth;
4188 rowHeight[rowNum] = tnHeight;
4189
4190 // prevent incomplete row from being heigher than the previous ones.
4191 // by grief-of-these-days
4192 var rowHeightLimit = Math.max(rowHasVertical ? maxRowHeightVertical : 0, rowHasHorizontal ? maxRowHeightHorizontal : 0);
4193 if( rowHeightLimit > 0 ) {
4194 rowHeight[rowNum] = Math.min(rowHeight[rowNum], rowHeightLimit);
4195 }
4196
4197 rowLastItem[rowNum] = i;
4198 }
4199 else {
4200 // new row after current item --> we need to adujet the row height to have enough space for the current thumbnail
4201 curWidth += gutterWidth+imageWidth;
4202 let ratio = (areaWidth - nbTnInCurrRow * borderWidth) / curWidth;
4203 let rH = Math.floor(tnHeight * ratio);
4204 rowHeight[rowNum] = rH;
4205
4206 // save the max row height for each thumb orientation.
4207 // by grief-of-these-days
4208 if( rowHasVertical ) {
4209 maxRowHeightVertical = Math.max( maxRowHeightVertical, rH );
4210 }
4211 if( rowHasHorizontal ) {
4212 maxRowHeightHorizontal = Math.max( maxRowHeightHorizontal, rH );
4213 }
4214
4215 rowLastItem[rowNum] = i;
4216 bNewRow = true;
4217 }
4218 cnt++;
4219 nbTnInCurrRow++;
4220 }
4221 }
4222
4223 rowNum = 0;
4224 curPosY = 0;
4225 lastPosX = 0;
4226 cnt = 0;
4227
4228 G.GOM.lastFullRow = 0; // display at leat 1 row (even if not full)
4229
4230 // second loop --> calculate each thumbnail size
4231 for( var i = 0; i < nbTn ; i++ ) {
4232 let curTn = G.GOM.items[i];
4233 if( curTn.imageWidth > 0 ) {
4234 let imageRatio = curTn.imageWidth / curTn.imageHeight;
4235 let imageWidth = Math.floor( imageRatio * rowHeight[rowNum] ); // border is already NOT included
4236
4237 if( i == rowLastItem[rowNum] ) {
4238 // row last item --> adjust image width because of rounding problems
4239 if( rowLastItem.length != (rowNum+1) ) {
4240 // last item in current row -> use the full remaining width
4241 imageWidth = areaWidth - lastPosX - borderWidth;
4242 }
4243 else {
4244 // very last item (on the last row)
4245 if( (lastPosX + gutterWidth + imageWidth + borderWidth ) > areaWidth ) {
4246 // reduce size if image is wider as the remaining space
4247 imageWidth = areaWidth - lastPosX - borderWidth;
4248 }
4249 }
4250 }
4251
4252 let rh = parseInt( rowHeight[rowNum] );
4253 imageWidth = parseInt( imageWidth );
4254
4255 // thumbnail image size
4256 curTn.resizedContentWidth = imageWidth;
4257 curTn.resizedContentHeight = rh;
4258 // thumbnail position and size
4259 curTn.width = imageWidth + borderWidth;
4260 curTn.height= rh + G.tn.labelHeight.get() + borderHeight;
4261 curTn.row = rowNum;
4262
4263 curTn.top = curPosY;
4264 let x = lastPosX;
4265 if( G.O.RTL) {
4266 x = areaWidth - lastPosX - curTn.width ;
4267 }
4268 curTn.left = x;
4269
4270 lastPosX += curTn.width + gutterWidth;
4271
4272 if( i == rowLastItem[rowNum] ) {
4273 // start a new row
4274 curPosY += curTn.height + gutterHeight;
4275 G.GOM.lastFullRow = rowNum - 1;
4276 rowNum++;
4277 lastPosX = 0;
4278 }
4279 cnt++;
4280 }
4281 else {
4282 return false;
4283 }
4284 }
4285
4286 if( false ) {
4287 var newTop = 0;
4288 if( typeof GOMidx !== 'undefined' ) {
4289 // hover effect on gallery (vs on thumbnail) --> experimental / not used
4290 if( G.GOM.albumIdx != -1 ) {
4291 var hoveredTn = G.GOM.items[GOMidx];
4292 // var item = G.I[hoveredTn.thumbnailIdx];
4293
4294 // hovered thumbnail
4295 hoveredTn.width += 40;
4296 hoveredTn.height += 40;
4297 // todo : left
4298
4299 for( var i = 0; i < nbTn ; i++ ) {
4300 var curTn = G.GOM.items[i];
4301 if( curTn.imageWidth > 0 ) {
4302 if( curTn.row == hoveredTn.row ) {
4303 // hovered row
4304 newTop = 40;
4305 if( hoveredTn.thumbnailIdx != curTn.thumbnailIdx ) {
4306 // not hovered thumbnail
4307 // curTn.resizedContentWidth+=10;
4308 // curTn.resizedContentHeight+=20;
4309 // curTn.width+=10;
4310 curTn.top += 30;
4311 curTn.width -= 20;
4312 curTn.height -= 20;
4313 }
4314 }
4315 else {
4316 // not hovered row
4317 if( curTn.row == 0 ) {
4318 // first row
4319 }
4320 else {
4321 curTn.top += newTop;
4322 }
4323 }
4324 }
4325 }
4326 }
4327 }
4328 }
4329
4330 G.GOM.displayArea.width = areaWidth;
4331 return true;
4332 }
4333
4334
4335 //----- MOSAIC LAYOUT
4336 // Grid using a user defined pattern layout
4337 // With this layout, a pattern definition is handeld a row
4338 function GallerySetLayoutMosaic() {
4339 var areaWidth = G.GOM.cache.areaWidth;
4340 var gutterHeight = G.tn.settings.GetResponsive('gutterHeight');
4341 var gutterWidth = G.tn.settings.GetResponsive('gutterWidth');
4342 var borderWidth = G.tn.opt.Get('borderHorizontal') * 2;
4343 var borderHeight = G.tn.opt.Get('borderVertical') * 2;
4344
4345 var nbTn = G.GOM.items.length;
4346 var row = 0;
4347 var h = 0;
4348 var n = 0;
4349
4350
4351 // first loop: evaluate the gallery width based on the first row
4352 var nbCols = 0;
4353 var maxW = 0;
4354 let mosaicPattern = G.tn.settings.getMosaic();
4355 for( var i = 0; i < nbTn ; i++ ) {
4356 let curPatternElt = mosaicPattern[n];
4357
4358 var cLeft = (curPatternElt.c - 1) * G.tn.defaultSize.getOuterWidth() + (curPatternElt.c - 1) * gutterWidth;
4359 var cWidth = curPatternElt.w * G.tn.defaultSize.getOuterWidth() + (curPatternElt.w - 1) * gutterWidth;
4360
4361 maxW = Math.max(maxW, cLeft + cWidth );
4362
4363 nbCols = Math.max(nbCols, (curPatternElt.c - 1) + curPatternElt.w );
4364
4365 n++;
4366 if( n >= mosaicPattern.length ) {
4367 // end of pattern
4368 break;
4369 }
4370 }
4371 var totalGutterWidth = (nbCols - 1) * gutterWidth;
4372 var scaleFactor = Math.min( (areaWidth - totalGutterWidth ) / ( maxW - totalGutterWidth ), 1);
4373
4374 // second loop: position all the thumbnails based on the layout pattern
4375 row = 0;
4376 n = 0;
4377 // let mosaicPattern = G.tn.settings.getMosaic();
4378 for( var i = 0; i < nbTn ; i++ ) {
4379 let curTn = G.GOM.items[i];
4380 let curPatternElt = mosaicPattern[n];
4381
4382 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)) ;
4383 if( row > 0 ) {
4384 curTn.top += gutterHeight;
4385 }
4386
4387 curTn.left = (curPatternElt.c - 1) * Math.round(G.tn.defaultSize.getOuterWidth()*scaleFactor) + (curPatternElt.c - 1) * gutterWidth;
4388
4389 curTn.height = Math.round(curPatternElt.h * G.tn.defaultSize.getOuterHeight() * scaleFactor) + (curPatternElt.h - 1) * gutterHeight + (G.tn.labelHeight.get() * curPatternElt.h);
4390 curTn.resizedContentHeight = curTn.height - G.tn.labelHeight.get() - borderHeight;
4391
4392 curTn.width = Math.round(curPatternElt.w * G.tn.defaultSize.getOuterWidth()*scaleFactor) + (curPatternElt.w - 1) * gutterWidth;
4393 curTn.resizedContentWidth = curTn.width - borderWidth ;
4394
4395 curTn.row = row;
4396 if( row == 0 ) {
4397 h=Math.max(h, curTn.top + curTn.height);
4398 }
4399
4400 n++;
4401 if( n >= mosaicPattern.length ) {
4402 // end pattern -> new line
4403 n = 0;
4404 row++;
4405 }
4406 }
4407
4408 G.GOM.displayArea.width = (maxW - totalGutterWidth) * scaleFactor + totalGutterWidth;
4409 return true;
4410 }
4411
4412
4413
4414 // --- GRID LAYOUT
4415 function GallerySetLayoutGrid() {
4416 var curPosX= 0,
4417 curPosY= 0,
4418 areaWidth= G.GOM.cache.areaWidth,
4419 gutterWidth= 0,
4420 gutterHeight= G.tn.settings.GetResponsive('gutterHeight'),
4421 maxCol= NbThumbnailsPerRow(areaWidth),
4422 w= 0,
4423 cols= [],
4424 curCol= 0,
4425 newAreaWidth = areaWidth,
4426 tnWidth= G.tn.defaultSize.getOuterWidth();
4427 var scaleFactor = 1;
4428 var nbTn= G.GOM.items.length;
4429 var borderWidth = G.tn.opt.Get('borderHorizontal') * 2;
4430 var borderHeight = G.tn.opt.Get('borderVertical') * 2;
4431
4432 // retrieve gutter width
4433 if( G.O.thumbnailAlignment == 'justified' ) {
4434 maxCol = Math.min( maxCol, nbTn);
4435 gutterWidth = (maxCol==1 ? 0 : (areaWidth-(maxCol*tnWidth))/(maxCol-1));
4436 }
4437 else {
4438 gutterWidth = G.tn.settings.GetResponsive('gutterWidth');
4439 }
4440
4441 // first loop to retrieve the real used width of the area (the evaluation is based on the content of the first line)
4442 // Retrieve the real used width of the area (the evaluation is based on the content of the first line)
4443 if( G.O.RTL || G.O.thumbnailAlignment == 'fillWidth' ) {
4444 // scaled --> evaluate scale factor and number of columns
4445 var totalGutterWidth = (maxCol-1) * gutterWidth;
4446 scaleFactor = (areaWidth - totalGutterWidth) / (maxCol*tnWidth);
4447 if( scaleFactor > 1 ) {
4448 maxCol++; // add one column and re-evaluate the scale factor
4449 }
4450 totalGutterWidth = (maxCol-1) * gutterWidth;
4451 scaleFactor = Math.min( (areaWidth - totalGutterWidth) / (maxCol*tnWidth), 1); // no upscale
4452 newAreaWidth = (maxCol*tnWidth) + totalGutterWidth;
4453 }
4454
4455
4456 G.GOM.lastFullRow = 0 ; // display at leat 1 row (even if not full)
4457 // var lastPosY = 0;
4458 var row = 0;
4459
4460 tnWidth = Math.round(tnWidth * scaleFactor);
4461 var contentWidth = tnWidth - borderWidth;
4462 var tnHeight = Math.round(G.tn.defaultSize.getOuterHeight() * scaleFactor) + G.tn.labelHeight.get();
4463 var contentHeight = Math.round( G.tn.defaultSize.getOuterHeight() * scaleFactor) - borderHeight;
4464
4465 // loop to position and to set size of all thumbnails
4466 for( var i = 0; i < nbTn ; i++ ) {
4467 if( curPosY == 0 ) {
4468 curPosX = curCol * (tnWidth + gutterWidth)
4469 cols[curCol] = curPosX;
4470 w = curPosX + tnWidth;
4471 }
4472 else {
4473 curPosX = cols[curCol];
4474 }
4475
4476 var x = curPosX;
4477 if( G.O.RTL ) {
4478 x = parseInt(newAreaWidth) - curPosX - tnWidth;
4479 }
4480
4481 // MANDATORY : set thumbnail position AND size
4482 var curTn=G.GOM.items[i];
4483 curTn.top = curPosY;
4484 curTn.left = x;
4485 curTn.height = tnHeight;
4486 curTn.width = tnWidth;
4487 // image size
4488 if( G.O.thumbnailAlignment == 'fillWidth' ) {
4489 curTn.resizedContentWidth = contentWidth;
4490 curTn.resizedContentHeight = contentHeight;
4491 }
4492 curTn.row = row;
4493 // lastPosY = curPosY;
4494
4495 curCol++;
4496 if( curCol >= maxCol ){
4497 // new line
4498 curCol = 0;
4499 curPosY += tnHeight + gutterHeight;
4500 G.GOM.lastFullRow = row;
4501 row++;
4502 }
4503 }
4504 G.GOM.displayArea.width = w;
4505
4506 return true;
4507 }
4508
4509
4510
4511
4512 //----- Display the thumbnails according to the calculated layout
4513 function GalleryDisplayPart1() {
4514 if( G.CSStransformName == null ) {
4515 G.$E.conTn.css( 'left' , '0px');
4516 }
4517 else {
4518 G.$E.conTn.css( G.CSStransformName , 'none');
4519 }
4520 // CacheViewport();
4521 }
4522
4523 function CacheViewport() {
4524 G.GOM.cache.viewport = getViewport();
4525 // G.GOM.cache.areaWidth = G.$E.conTnParent.width();
4526 G.GOM.cache.areaWidth = G.$E.base.width();
4527
4528 // position of the gallery container
4529 // we use the position of the loadingbar because :
4530 // - the gallery may be wrong positioned due to one display animation currently running
4531 // - the loadingbar is never animated and positioned just before the gallery container
4532 //G.GOM.cache.containerOffset = G.$E.conTnParent.offset();
4533 if( !G.O.lightboxStandalone ) {
4534 G.GOM.cache.containerOffset = G.$E.conLoadingB.offset();
4535 }
4536 }
4537
4538
4539
4540 function GalleryDisplayPart2( forceTransition ) {
4541 CacheViewport();
4542
4543 var nbTn = G.GOM.items.length;
4544 G.GOM.itemsDisplayed = 0;
4545 var threshold = 50;
4546 var cnt = 0; // counter for delay between each thumbnail display
4547
4548
4549 GalleryRenderGetInterval();
4550
4551 for( var i = 0; i < nbTn ; i++ ) {
4552 let curTn = G.GOM.items[i];
4553 if( i >= G.GOM.displayInterval.from && cnt < G.GOM.displayInterval.len ) {
4554 curTn.inDisplayArea = true;
4555 if( forceTransition ) {
4556 curTn.neverDisplayed = true;
4557 }
4558 G.GOM.itemsDisplayed++;
4559 cnt++;
4560 }
4561 else{
4562 curTn.inDisplayArea = false;
4563 }
4564 }
4565
4566 // bottom of the gallery (pagination, more button...)
4567 GalleryBottomManage();
4568
4569 var tnToDisplay = [];
4570 var tnToReDisplay = [];
4571
4572 CacheViewport();
4573 G.GOM.clipArea.top = -1;
4574 cnt = 0 ;
4575 var lastTnIdx = -1;
4576 G.GOM.clipArea.height = 0;
4577 // NOTE: loop always the whole GOM.items --> in case an already displayed thumbnail needs to be removed
4578 for( var i = 0; i < nbTn ; i++ ) {
4579 let curTn = G.GOM.items[i];
4580 if( curTn.inDisplayArea ) {
4581 if( G.GOM.clipArea.top == -1 ) {
4582 G.GOM.clipArea.top = curTn.top;
4583 }
4584 if( (curTn.top - G.GOM.clipArea.top) <= -1 ) {
4585 // with mosaic layout, the first thumbnail may not give the top position
4586 G.GOM.clipArea.top = curTn.top;
4587 }
4588
4589 G.GOM.clipArea.height = Math.max( G.GOM.clipArea.height, curTn.top-G.GOM.clipArea.top + curTn.height);
4590
4591 if( curTn.neverDisplayed ) {
4592 // thumbnail is not displayed -> check if in viewport to display or not
4593 var top = G.GOM.cache.containerOffset.top + (curTn.top - G.GOM.clipArea.top);
4594 // var left=containerOffset.left+curTn.left;
4595 if( (top + curTn.height) >= (G.GOM.cache.viewport.t - threshold) && top <= (G.GOM.cache.viewport.t + G.GOM.cache.viewport.h + threshold) ) {
4596 // build thumbnail
4597 let item = G.I[curTn.thumbnailIdx];
4598 if( item.$elt == null ) {
4599 // ThumbnailBuild( item, curTn.thumbnailIdx, i, (i+1) == nbTn );
4600 ThumbnailBuild( item, curTn.thumbnailIdx, i );
4601 }
4602 tnToDisplay.push({idx:i, delay:cnt});
4603 cnt++;
4604 }
4605 }
4606 else {
4607 tnToReDisplay.push({idx: i, delay: 0});
4608 }
4609 // G.GOM.itemsDisplayed++;
4610 lastTnIdx = i;
4611 }
4612 else {
4613 curTn.displayed = false;
4614 let item = G.I[curTn.thumbnailIdx];
4615 if( item.$elt != null ){
4616 item.$elt.css({ opacity: 0, display: 'none' });
4617 }
4618 }
4619 }
4620
4621 var areaWidth = G.$E.conTnParent.width();
4622
4623 // set gallery area really used size
4624 // if( G.GOM.displayArea.width != G.GOM.displayAreaLast.width || G.GOM.displayArea.height != G.GOM.displayAreaLast.height ) {
4625 if( G.GOM.displayArea.width != G.GOM.displayAreaLast.width || G.GOM.clipArea.height != G.GOM.displayAreaLast.height ) {
4626 G.$E.conTn.width( G.GOM.displayArea.width ).height( G.GOM.clipArea.height );
4627 G.GOM.displayAreaLast.width = G.GOM.displayArea.width;
4628 G.GOM.displayAreaLast.height = G.GOM.clipArea.height;
4629 // G.GOM.displayAreaLast.height=G.GOM.displayArea.height-G.GOM.clipArea.top;
4630 }
4631
4632 if( areaWidth != G.$E.conTnParent.width() ) {
4633 // gallery area width changed since layout calculation (for example when a scrollbar appeared)
4634 // so we need re-calculate the layout before displaying the thumbnails
4635 G.GOM.cache.areaWidth = G.$E.conTnParent.width();
4636 GallerySetLayout();
4637 GalleryDisplayPart1();
4638 GalleryDisplayPart2( forceTransition );
4639 return;
4640 }
4641
4642 // counter of not displayed images (is displayed on the last thumbnail)
4643 if( G.layout.support.rows ) {
4644 if( G.galleryDisplayMode.Get() == 'ROWS' || (G.galleryDisplayMode.Get() == 'FULLCONTENT' && G.galleryLastRowFull.Get() && G.GOM.lastFullRow != -1) ){
4645 if( lastTnIdx < (nbTn - 1) ) {
4646 G.GOM.lastDisplayedIdxNew = lastTnIdx;
4647 }
4648 else {
4649 G.GOM.lastDisplayedIdxNew =- 1;
4650 }
4651 // remove last displayed counter
4652 if( G.GOM.lastDisplayedIdx != -1 ) {
4653 let item = G.I[G.GOM.items[G.GOM.lastDisplayedIdx].thumbnailIdx];
4654 item.$getElt('.nGY2GThumbnailIconsFullThumbnail').html('');
4655 }
4656 }
4657 }
4658
4659
4660 // batch set position (and display animation) to all thumbnails
4661 // first display newly built thumbnails
4662 if( G.tn.opt.Get('displayOrder') == 'random' ) {
4663 NGY2Tools.AreaShuffle( tnToDisplay );
4664 }
4665 var nbBuild = tnToDisplay.length;
4666 G.GOM.thumbnails2Display=[];
4667 for( var i = 0; i < nbBuild ; i++ ) {
4668 // ThumbnailSetPosition(tnToDisplay[i].idx, tnToDisplay[i].delay+10);
4669 ThumbnailSetPosition(tnToDisplay[i].idx, i);
4670 }
4671
4672 // then re-position already displayed thumbnails
4673 if( G.tn.opt.Get('displayOrder') == 'random' ) {
4674 NGY2Tools.AreaShuffle( tnToReDisplay );
4675 }
4676 var n = tnToReDisplay.length;
4677 for( var i = 0; i < n ; i++ ) {
4678 // ThumbnailSetPosition(tnToReDisplay[i].idx, nbBuild+1);
4679 ThumbnailSetPosition(tnToReDisplay[i].idx, i);
4680 }
4681
4682 ThumbnailDisplayAnimBatch();
4683
4684 if( G.tn.opt.Get('displayTransition') == 'NONE' ) {
4685 G.galleryResizeEventEnabled = true;
4686 // GalleryThumbnailSliderBuildAndStart(); // image slider on last displayed thumbnail
4687 TriggerCustomEvent('galleryDisplayed');
4688 }
4689 else {
4690 // setTimeout(function() {
4691 requestTimeout( function() {
4692 // change value after the end of the display transistion of the newly built thumbnails
4693 G.galleryResizeEventEnabled = true;
4694 // GalleryThumbnailSliderBuildAndStart(); // image slider on last displayed thumbnail
4695 TriggerCustomEvent('galleryDisplayed');
4696 }, nbBuild * G.tn.opt.Get('displayInterval'));
4697 }
4698
4699 }
4700
4701
4702 // Thumbnail: set the new position
4703 function ThumbnailSetPosition( GOMidx, cnt ) {
4704 var newTop= 0;
4705 var curTn= G.GOM.items[GOMidx];
4706 var idx= G.GOM.items[GOMidx].thumbnailIdx;
4707 var item= G.I[idx];
4708
4709 if( curTn.neverDisplayed ) {
4710 // thumbnail is built but has never been displayed (=first display)
4711 var top = curTn.top - G.GOM.clipArea.top;
4712 if( G.tn.opt.Get('stacks') > 0 ) {
4713 // we have stacks -> do not display them here. They will be displayed at the end of the display animation
4714 item.$elt.last().css({ display: 'block'});
4715 item.$elt.css({ top: top , left: curTn.left });
4716 }
4717 else {
4718 item.$elt.css({ display: 'block', top: top , left: curTn.left });
4719 }
4720 newTop=top;
4721
4722 // display the image of the thumbnail when fully loaded
4723 if( G.O.thumbnailWaitImageLoaded === true ) {
4724 var gi_imgLoad = ngimagesLoaded( item.$getElt('.nGY2TnImg2') );
4725 gi_imgLoad.on( 'progress', function( instance, image ) {
4726 if( image.isLoaded ) {
4727 var albumIdx = image.img.getAttribute('data-albumidx');
4728 if( albumIdx == G.GOM.albumIdx ) {
4729 // ignore event if not on current album
4730 var idx = image.img.getAttribute('data-idx');
4731 G.I[idx].ThumbnailImageReveal();
4732 }
4733 }
4734 });
4735 }
4736 // display the thumbnail
4737 ThumbnailAppear(GOMidx, cnt);
4738
4739 curTn.displayed = true;
4740 curTn.neverDisplayed = false;
4741 }
4742 else {
4743 var topOld = G.GOM.cache.containerOffset.top + item.top;
4744 var top = G.GOM.cache.containerOffset.top + (curTn.top - G.GOM.clipArea.top);
4745 newTop = curTn.top - G.GOM.clipArea.top;
4746 var vp = G.GOM.cache.viewport;
4747 if( G.O.thumbnailDisplayOutsideScreen || ( ( (topOld + curTn.height) >= (vp.t - vp.h) && topOld <= (vp.t + vp.h * 4) ) ||
4748 ( (top + curTn.height) >= (vp.t - vp.h) && top <= (vp.t + vp.h * 4) ) ) ) {
4749 // thumbnail positioned in enlarged viewport (viewport + 4 x viewport height) (v1.5: changed from 2 to 4)
4750 if( curTn.displayed ) {
4751 // thumbnail is displayed
4752 if( item.top != curTn.top || item.left != curTn.left ) {
4753 // set position
4754 if( G.O.galleryResizeAnimation == true ) {
4755 // with transition
4756 var tweenable = new NGTweenable();
4757 tweenable.tween({
4758 from: { top: item.top, left: item.left, height: item.height, width: item.width },
4759 to: { top: newTop, left: curTn.left, height: curTn.height, width: curTn.width },
4760 attachment: { $e: item.$elt },
4761 duration: 100,
4762 delay: cnt * G.tn.opt.Get('displayInterval') / 5,
4763 // easing: 'easeInOutQuad',
4764 easing: 'easeOutQuart',
4765 step: function (state, att) {
4766 // window.ng_draf( function() {
4767 att.$e.css(state);
4768 // });
4769 },
4770 finish: function (state, att) {
4771 var _this=this;
4772 // window.ng_draf( function() {
4773 _this.dispose();
4774 // });
4775 }
4776 });
4777 }
4778 else {
4779 // set position without transition
4780 // item.$elt.css({ top: curTn.top , left: curTn.left });
4781 item.$elt.css({ top: newTop , left: curTn.left });
4782 }
4783 }
4784 }
4785 else {
4786 // re-display thumbnail
4787 curTn.displayed = true;
4788 // item.$elt.css({ display: 'block', top: curTn.top , left: curTn.left, opacity:1 });
4789 item.$elt.css({ display: 'block', top: newTop, left: curTn.left, opacity: 1 });
4790 ThumbnailAppearFinish(item);
4791 }
4792 }
4793 else {
4794 // undisplay thumbnail if not in viewport+margin --> performance gain
4795 curTn.displayed = false;
4796 item.$elt.css({ display: 'none'});
4797 }
4798 }
4799 item.left = curTn.left;
4800 item.top = newTop;
4801
4802 // set new size if changed
4803 if( item.width != curTn.width || item.height != curTn.height ) {
4804 item.$elt.css({ width: curTn.width , height: curTn.height });
4805 item.width = curTn.width;
4806 item.height = curTn.height;
4807
4808 // if( curTn.resizedContentWidth > 0 ) {
4809 // resize also the content (=image)
4810 if( item.resizedContentWidth != curTn.resizedContentWidth || item.resizedContentHeight != curTn.resizedContentHeight ) {
4811 if( item.kind == 'albumUp' ) {
4812 // item.$getElt('.nGY2GThumbnailAlbumUp').css({'height': curTn.resizedContentHeight, 'width': curTn.resizedContentWidth});
4813 }
4814 else {
4815 item.$getElt('.nGY2GThumbnailImage').css({'height': curTn.resizedContentHeight, 'width': curTn.resizedContentWidth});
4816
4817 if( G.layout.engine == 'JUSTIFIED' ) {
4818 item.$getElt('.nGY2GThumbnailImg').css({'height': curTn.resizedContentHeight, 'width': curTn.resizedContentWidth});
4819 }
4820 }
4821 item.resizedContentWidth = curTn.resizedContentWidth;
4822 item.resizedContentHeight = curTn.resizedContentHeight;
4823 }
4824 }
4825
4826
4827 // add counter of remaining (not displayed) images
4828 if( G.GOM.lastDisplayedIdxNew == GOMidx && G.layout.support.rows ) {
4829 if( (G.galleryDisplayMode.Get() == 'ROWS' && G.galleryMaxRows.Get() > 0) || (G.galleryDisplayMode.Get() == 'FULLCONTENT' && G.galleryLastRowFull.Get() && G.GOM.lastFullRow != -1) ){
4830 // number of items
4831 var nb = G.GOM.items.length - GOMidx - 1;
4832 if( item.albumID != '0' && G.O.thumbnailLevelUp ) {
4833 nb--;
4834 }
4835
4836 if( nb > 0 ) {
4837 // display counter
4838 if( G.O.thumbnailOpenInLightox || G.O.thumbnailSliderDelay > 0 ) {
4839 item.$getElt('.nGY2GThumbnailIconsFullThumbnail').html( '+' + nb);
4840 }
4841
4842 // if( G.layout.engine == 'GRID' && G.GOM.slider.hostItem != G.GOM.NGY2Item(GOMidx) ) {
4843 // image slider on last displayed thumbnail
4844 if( G.O.thumbnailLabel.get('position') != 'right' && G.O.thumbnailLabel.get('position') != 'left' ) {
4845 if( G.GOM.slider.hostItem != G.GOM.NGY2Item(GOMidx) ) {
4846
4847 // set current slider back to initial content
4848 GalleryThumbnailSliderSetContent( G.GOM.slider.hostItem );
4849 // new slider
4850 G.GOM.slider.hostIdx = GOMidx;
4851 G.GOM.slider.hostItem = G.GOM.NGY2Item(GOMidx);
4852 G.GOM.slider.nextIdx = GOMidx;
4853 G.GOM.slider.currentIdx = GOMidx;
4854 GalleryThumbnailSliderBuildAndStart(); // image slider on last displayed thumbnail
4855 // GalleryThumbnailSliderSetNextContent();
4856 }
4857 }
4858 }
4859 else {
4860 // reset slider content to initial content because all thumbnails are displayed
4861 GalleryThumbnailSliderSetContent( G.GOM.slider.hostItem );
4862 G.GOM.slider.hostIdx = -1;
4863 }
4864
4865 G.GOM.lastDisplayedIdx = GOMidx;
4866 }
4867 }
4868
4869 }
4870
4871 // ---------------------
4872 // replace image on last thumbnails with not displayed ones (mode ROWS or FULLCONTENT with galleryLastRowFull enabled)
4873 // function GalleryLastThumbnailSlideImage() {
4874 function GalleryThumbnailSliderBuildAndStart() {
4875
4876 if( G.O.thumbnailSliderDelay == 0 || G.GOM.slider.hostIdx == -1 ) {
4877 return;
4878 }
4879 clearTimeout(G.GOM.slider.timerID);
4880
4881 var item = G.GOM.slider.hostItem;
4882
4883 // dupplicate image layer -> for the next image
4884 if( item.$getElt('.nGY2TnImgNext').length == 0 ) {
4885 item.$getElt('.nGY2TnImg').clone().removeClass('nGY2TnImg').addClass('nGY2TnImgNext').insertAfter(item.$getElt('.nGY2TnImg'));
4886 item.$getElt('.nGY2TnImgBack').clone().removeClass('nGY2TnImgBack').addClass('nGY2TnImgBackNext').insertAfter(item.$getElt('.nGY2TnImg', true));
4887 item.$getElt('.nGY2GThumbnailImage', true); // important -> refresh the cache
4888 item.$getElt('.nGY2GThumbnailImg', true); // important -> refresh the cache
4889 }
4890
4891 item.CSSTransformSet('.nGY2TnImgNext', 'translateX', '100%', true);
4892 item.CSSTransformApply( '.nGY2TnImgNext' );
4893 item.CSSTransformSet('.nGY2TnImgBackNext', 'translateX', '100%', true);
4894 item.CSSTransformApply( '.nGY2TnImgBackNext' );
4895
4896 GalleryThumbnailSliderSetNextContent();
4897
4898 // clearTimeout(G.GOM.slider.timerID);
4899 // G.GOM.slider.timerID = setTimeout(function(){ GalleryThumbnailSliderStartTransition() }, G.O.thumbnailSliderDelay);
4900 G.GOM.slider.timerID = requestTimeout(function(){ GalleryThumbnailSliderStartTransition() }, G.O.thumbnailSliderDelay);
4901 }
4902
4903
4904 function GalleryThumbnailSliderSetNextContent() {
4905
4906 G.GOM.slider.nextIdx++;
4907 if( G.GOM.slider.nextIdx >= G.GOM.items.length ) {
4908 G.GOM.slider.nextIdx = G.GOM.slider.hostIdx;
4909 }
4910
4911 // new image
4912 var newItem = G.GOM.NGY2Item(G.GOM.slider.nextIdx);
4913 // var imgBlurred = G.emptyGif;
4914 var bgImg = "url('" + G.emptyGif + "')";
4915 if( newItem.imageDominantColors != null ) {
4916 // imgBlurred = newItem.imageDominantColors;
4917 bgImg = "url('" + newItem.imageDominantColors + "')";
4918 }
4919 G.GOM.slider.hostItem.$getElt('.nGY2TnImgBackNext', true).css({'background-image': bgImg, opacity: 1 });
4920 G.GOM.slider.hostItem.$getElt('.nGY2TnImgNext', true).css({ 'background-image': "url('" + newItem.thumbImg().src + "')", opacity: 1 });
4921 G.GOM.slider.hostItem.$getElt('.nGY2TnImgNext .nGY2GThumbnailImg', true).attr('src', newItem.thumbImg().src );
4922
4923
4924 }
4925
4926 // thumbnail slider - transition from one image to the next one
4927 function GalleryThumbnailSliderStartTransition() {
4928
4929 if( G.GOM.slider.hostItem.$getElt() != null ) {
4930
4931 // slider transition
4932 var tweenable = new NGTweenable();
4933 G.GOM.slider.tween = tweenable;
4934 tweenable.tween({
4935 from: { 'left': 100 },
4936 to: { 'left': 0 },
4937 duration: 800,
4938 delay: 0,
4939 // easing: 'easeInOutQuad',
4940 easing: 'easeOutQuart',
4941
4942 step: function (state) {
4943 if( G.GOM.slider.hostItem.$getElt() == null ) {
4944 // the thumbnail may have been destroyed since the start of the animation
4945 G.GOM.slider.tween.stop(false);
4946 return;
4947 }
4948
4949 // window.ng_draf( function() {
4950 // slide current content
4951 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgBack', 'translateX', -(100 - state.left) + '%');
4952 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgBack' );
4953 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImg', 'translateX', -(100 - state.left) + '%');
4954 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImg' );
4955
4956 // slide new content
4957 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgBackNext', 'translateX', state.left + '%');
4958 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgBackNext' );
4959 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgNext', 'translateX', state.left + '%');
4960 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgNext' );
4961 // });
4962
4963
4964 },
4965 finish: function (state) {
4966 if( G.GOM.slider.hostItem.$getElt() == null ) {
4967 // the thumbnail may be destroyed since the start of the animation
4968 return;
4969 }
4970
4971 if( G.GOM.NGY2Item(G.GOM.slider.nextIdx) == null ) { return; } // item does not exist anymore
4972
4973 // window.ng_draf( function() {
4974 // set new content as current content
4975 GalleryThumbnailSliderSetContent( G.GOM.NGY2Item(G.GOM.slider.nextIdx) );
4976 G.GOM.slider.currentIdx = G.GOM.slider.nextIdx;
4977 GalleryThumbnailSliderSetNextContent();
4978
4979 clearTimeout(G.GOM.slider.timerID);
4980 // G.GOM.slider.timerID=setTimeout(function(){ GalleryThumbnailSliderStartTransition() }, G.O.thumbnailSliderDelay);
4981 G.GOM.slider.timerID = requestTimeout(function(){ GalleryThumbnailSliderStartTransition() }, G.O.thumbnailSliderDelay);
4982 // });
4983 }
4984 });
4985 }
4986 }
4987
4988 // set main content of the thumbnail hosting the slider
4989 // hide the elements for the next content of the slider
4990 function GalleryThumbnailSliderSetContent( ngy2itemContent ) {
4991 if( G.GOM.slider.hostIdx == -1 ) { return; }
4992
4993 if( G.GOM.slider.tween != null ) {
4994 if( G.GOM.slider.tween._isTweening == true ) {
4995 G.GOM.slider.tween.stop(false);
4996 }
4997 }
4998
4999 var bgImg = "url('" + G.emptyGif + "')";
5000 if( ngy2itemContent.imageDominantColors != null ) {
5001 bgImg = "url('" + ngy2itemContent.imageDominantColors + "')";
5002 }
5003 G.GOM.slider.hostItem.$getElt('.nGY2TnImgBack').css('background-image', bgImg);
5004 G.GOM.slider.hostItem.$getElt('.nGY2TnImg').css('background-image', "url('" + ngy2itemContent.thumbImg().src + "')" );
5005 G.GOM.slider.hostItem.$getElt('.nGY2TnImg .nGY2GThumbnailImg').attr('src', ngy2itemContent.thumbImg().src );
5006
5007 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgBack', 'translateX', '0');
5008 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgBack' );
5009 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImg', 'translateX', '0');
5010 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImg' );
5011
5012 // place the containers for the next image slider outside of the thumbnail (=hidden)
5013 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgBackNext', 'translateX', '100%', true);
5014 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgBackNext' );
5015 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgNext', 'translateX', '100%', true);
5016 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgNext' );
5017
5018 // set new title and description
5019 if( G.O.thumbnailLabel.get('display') == true ) {
5020 var icons = G.O.icons.thumbnailAlbum;
5021 if( ngy2itemContent.kind != 'album' ) {
5022 icons = G.O.icons.thumbnailImage;
5023 }
5024 G.GOM.slider.hostItem.$getElt('.nGY2GThumbnailTitle').html(icons + getThumbnailTitle(ngy2itemContent));
5025 G.GOM.slider.hostItem.$getElt('.nGY2GThumbnailDescription').html(icons + getTumbnailDescription(ngy2itemContent));
5026 }
5027 }
5028
5029
5030
5031 // Compute the height of the label part of a thumbnail (title+description, both single line)
5032 function ThumbnailGetLabelHeight() {
5033 var newElt = [],
5034 newEltIdx = 0;
5035
5036 // if( G.O.thumbnailLabel.get('display') == false && G.tn.toolbar.getWidth(item) <= 0 ) {
5037 if( G.O.thumbnailLabel.get('display') == false ) {
5038 return 0;
5039 }
5040
5041 // var desc='';
5042 // if( G.O.thumbnailLabel.get('displayDescription') == true ) {
5043 // desc = 'aAzZjJ';
5044 // }
5045
5046 // visibility set to hidden
5047 newElt[newEltIdx++] = '<div class="nGY2GThumbnail ' + G.O.theme + '" style="display:block;visibility:hidden;position:absolute;top:-9999px;left:-9999px;" ><div class="nGY2GThumbnailSub">';
5048 if( G.O.thumbnailLabel.get('display') == true ) {
5049 // Labels: title and description
5050 newElt[newEltIdx++] = ' <div class="nGY2GThumbnailLabel" '+ G.tn.style.getLabel() +'>';
5051 newElt[newEltIdx++] = ' <div class="nGY2GThumbnailAlbumTitle" '+G.tn.style.getTitle()+'>aAzZjJ</div>';
5052 if( G.O.thumbnailLabel.get('displayDescription') == true ) {
5053 newElt[newEltIdx++] = ' <div class="nGY2GThumbnailDescription" '+G.tn.style.getDesc()+'>'+'aAzZjJ'+'</div>';
5054 }
5055 newElt[newEltIdx++] = ' </div>';
5056 }
5057
5058 newElt[newEltIdx++] = '</div></div>';
5059
5060 var $newDiv = jQuery(newElt.join('')).appendTo(G.$E.conTn);
5061 var h = $newDiv.find('.nGY2GThumbnailLabel').outerHeight(true);
5062 $newDiv.remove();
5063
5064 return h;
5065 }
5066
5067 function ThumbnailBuildStacks( bgColor ) {
5068 var ns=G.tn.opt.Get('stacks');
5069 if( ns == 0 ) { return ''; }
5070
5071 var s='';
5072 for( var i=0; i<ns; i++ ) {
5073 s='<div class="nGY2GThumbnailStack " style="display:none;'+bgColor+'"></div>'+s;
5074 }
5075 return s;
5076 }
5077
5078 //----- Build one UP thumbnail (=navigation thumbnail)
5079 function ThumbnailBuildAlbumpUp( item, GOMidx ) {
5080 // function ThumbnailBuildAlbumpUp( item, idx, GOMidx ) {
5081 var newElt = [],
5082 newEltIdx = 0;
5083
5084 var mp = '';
5085 if( G.O.thumbnailOpenInLightox === false ) {
5086 mp = 'cursor:default;'
5087 }
5088
5089 newElt[newEltIdx++] = ThumbnailBuildStacks('') + '<div class="nGY2GThumbnail" style="display:none;opacity:0;' + mp + '" >';
5090 newElt[newEltIdx++] = ' <div class="nGY2GThumbnailSub">';
5091
5092 var h=G.tn.defaultSize.getHeight(),
5093 w=G.tn.defaultSize.getWidth();
5094
5095 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>';
5096 // newElt[newEltIdx++] = ' <div class="nGY2GThumbnailAlbumUp" style="width:'+w+'px;height:'+h+'px;">'+G.O.icons.thumbnailAlbumUp+'</div>';
5097 newElt[newEltIdx++] = ' <div class="nGY2GThumbnailAlbumUp" >'+G.O.icons.thumbnailAlbumUp+'</div>';
5098 newElt[newEltIdx++] = ' </div>';
5099 newElt[newEltIdx++] = '</div>';
5100
5101 var $newDiv = jQuery(newElt.join('')).appendTo(G.$E.conTn); //.animate({ opacity: 1},1000, 'swing'); //.show('slow'); //.fadeIn('slow').slideDown('slow');
5102
5103 item.$elt = $newDiv;
5104 $newDiv.data('index', GOMidx);
5105 item.$getElt('.nGY2GThumbnailImg').data('index', GOMidx);
5106
5107 return;
5108 }
5109
5110
5111 //----- Build one thumbnail
5112 function ThumbnailBuild( item, idx, GOMidx ) {
5113 // function ThumbnailBuild( item, idx, GOMidx, lastOne ) {
5114 item.eltTransform = [];
5115 item.eltFilter = [];
5116 item.hoverInitDone = false;
5117 item.$Elts = [];
5118
5119 if( item.kind == 'albumUp' ) {
5120 ThumbnailBuildAlbumpUp( item, GOMidx);
5121 return;
5122 }
5123
5124 var newElt = [],
5125 newEltIdx = 0;
5126
5127 var mp = '';
5128 if( G.O.thumbnailOpenInLightox === false ) {
5129 mp = 'cursor:default;'
5130 }
5131
5132 // var src = encodeURI(item.thumbImg().src),
5133 var src = (item.thumbImg().src).replace(/'/g, "%27"), // replace single quote with %27
5134 sTitle = getThumbnailTitle(item);
5135
5136 // image background -> visible during image download
5137 var bg = '';
5138 var bgImg = "background-image: url('" + G.emptyGif + "');";
5139 if( item.imageDominantColors != null ) {
5140 // dominant colorS (blurred preview image)
5141 bgImg = "background-image: url('" + item.imageDominantColors + "');";
5142 }
5143 else {
5144 // dominant color -> background color
5145 if( item.imageDominantColor != null ) {
5146 bg = 'background-color:' + item.imageDominantColor + ';';
5147 }
5148 else {
5149 bgImg = '';
5150 }
5151 }
5152
5153 var op = 'opacity:1;';
5154 if( G.O.thumbnailWaitImageLoaded == true ) {
5155 op = 'opacity:0;';
5156 }
5157
5158 // ##### thumbnail containers (with stacks)
5159 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" : "" ) + '">';
5160
5161
5162 // image size
5163 var w = G.tn.settings.getW();
5164 var h = G.tn.settings.getH();
5165 if( G.tn.settings.getMosaic() !== null ) {
5166 // mosaic layout ->
5167 w = G.GOM.items[GOMidx].width;
5168 h = G.GOM.items[GOMidx].height;
5169 }
5170
5171 var bgSize = 'contain';
5172 if( G.tn.opt.Get('crop') ) {
5173 bgSize = 'cover'; // thumbnail image will be cropped to fit in the thumbnail (no black border)
5174 }
5175
5176 // ##### layer for image background (color, dominant color, blurred preview)
5177 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;";
5178 newElt[newEltIdx++]='<div class="nGY2GThumbnailImage nGY2TnImgBack" style="' + s1 + '"></div>';
5179
5180 // #### layer for image
5181 var s2 = op + "position: absolute; top: 0px; left: 0px; width:" + w + "px; height:" + h + "px; background-image: url('" + src + "'); background-position: center center; background-repeat: no-repeat; background-size:" + bgSize + "; overflow: hidden;";
5182 newElt[newEltIdx++]='<div class="nGY2GThumbnailImage nGY2TnImg" style="' + s2 + '">';
5183 newElt[newEltIdx++]=' <img class="nGY2GThumbnailImg nGY2TnImg2" src="' + src + '" alt="' + sTitle + '" style="opacity:0;" data-idx="' + idx + '" data-albumidx="' + G.GOM.albumIdx + '" >';
5184 newElt[newEltIdx++]='</div>';
5185
5186 // ##### layer for user customization purposes
5187 newElt[newEltIdx++]='<div class="nGY2GThumbnailCustomLayer"></div>';
5188
5189 // ##### layer for labels (title + description and their icons)
5190 if( G.O.thumbnailLabel.get('display') == true ) {
5191 // Labels: title and description
5192 newElt[newEltIdx++]= ' <div class="nGY2GThumbnailLabel" '+ G.tn.style.getLabel(item) + '>';
5193 if( item.kind == 'album' ) {
5194 // album kind
5195 newElt[newEltIdx++]= ' <div class="nGY2GThumbnailTitle nGY2GThumbnailAlbumTitle" ' + G.tn.style.getTitle() + '>' + G.O.icons.thumbnailAlbum + sTitle + '</div>';
5196 }
5197 else {
5198 // image/media kind
5199 newElt[newEltIdx++]= ' <div class="nGY2GThumbnailTitle nGY2GThumbnailImageTitle" ' + G.tn.style.getTitle() + '>' + G.O.icons.thumbnailImage + sTitle + '</div>';
5200 }
5201 newElt[newEltIdx++]= ' <div class="nGY2GThumbnailDescription" ' + G.tn.style.getDesc() + '>' + getTumbnailDescription(item) + '</div>';
5202 newElt[newEltIdx++]= ' </div>';
5203 }
5204
5205 // ##### layer for tools
5206 // newElt[newEltIdx++] = ThumbnailBuildTools(item, lastOne);
5207 newElt[newEltIdx++] = ThumbnailBuildTools(item);
5208
5209 // close containers
5210 newElt[newEltIdx++]='</div></div>';
5211
5212 var $newDiv =jQuery(newElt.join('')).appendTo(G.$E.conTn);
5213
5214 item.$elt=$newDiv;
5215 $newDiv.data('index',GOMidx);
5216 item.$getElt('.nGY2GThumbnailImg').data('index',GOMidx);
5217
5218 // Custom init function
5219 var fu=G.O.fnThumbnailInit;
5220 if( fu !== null ) {
5221 typeof fu == 'function' ? fu($newDiv, item, GOMidx) : window[fu]($newDiv, item, GOMidx);
5222 }
5223
5224 if( item.title != 'image gallery by nanogallery2 [build]' ) {
5225 ThumbnailOverInit(GOMidx);
5226 }
5227
5228 return ;
5229 }
5230
5231
5232 // Thumbnail layer for tools (toolbars and counter)
5233 function ThumbnailBuildTools( item ) {
5234
5235 // toolbars
5236 var tb = ThumbnailBuildToolbarOne(item, 'topLeft') + ThumbnailBuildToolbarOne(item, 'topRight') + ThumbnailBuildToolbarOne(item, 'bottomLeft') + ThumbnailBuildToolbarOne(item, 'bottomRight');
5237
5238 // counter of not displayed images
5239 tb += '<div class="nGY2GThumbnailIconsFullThumbnail"></div>';
5240
5241 return tb;
5242 }
5243
5244 function ThumbnailBuildToolbarOne( item, position ) {
5245 var toolbar = '';
5246 var tb = G.tn.toolbar.get(item);
5247 var width = { xs:0, sm:1, me:2, la:3, xl:4 };
5248 var cnt = 0;
5249
5250 if( tb[position] != '' ) {
5251 var pos='top: 0; right: 0; text-align: right;'; // 'topRight' and default
5252 switch( position ) {
5253 case 'topLeft':
5254 pos = 'top: 0; left: 0; text-align: left;';
5255 break;
5256 case 'bottomRight':
5257 pos = 'bottom: 0; right: 0; text-align: right;';
5258 break;
5259 case 'bottomLeft':
5260 pos = 'bottom: 0; left: 0; text-align: left;';
5261 break;
5262 }
5263
5264 toolbar += ' <ul class="nGY2GThumbnailIcons" style="' + pos + '">';
5265
5266 var icons = tb[position].split(',');
5267 var nb = icons.length;
5268 for( var i = 0; i < nb; i++ ) {
5269 var icon = icons[i].replace(/^\s*|\s*$/, ''); //trim trailing/leading whitespace
5270
5271 var minWidth = icon.substring(0,2).toLowerCase();
5272 var tIcon = icon;
5273 var display = true;
5274 if( /xs|sm|me|la|xl/i.test(minWidth) ) {
5275 // check visbility (depending on screen width)
5276 if( width[minWidth] > width[G.GOM.curWidth] ) {
5277 display = false;
5278 }
5279 tIcon = icon.substring(2);
5280 }
5281
5282 if( display ) {
5283 var sp=(i+1<nb ? '&nbsp;' :'');
5284 switch( tIcon ) {
5285 case 'COUNTER':
5286 if( item.kind == 'album' ) {
5287 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="">';
5288 toolbar += ' <div class="nGY2GThumbnailIconImageCounter"></div>';
5289 toolbar += ' <div class="nGY2GThumbnailIconText">' + G.O.icons.thumbnailCounter+Math.max((item.getContentLength(false)),item.numberItems) + sp + '</div>';
5290 toolbar += ' </li>';
5291 cnt++;
5292 }
5293 break;
5294 case 'COUNTER2':
5295 if( item.kind == 'album' ) {
5296 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="">';
5297 toolbar += ' <div class="nGY2GThumbnailIconTextBadge">' + G.O.icons.thumbnailCounter+Math.max((item.getContentLength(false)),item.numberItems) + sp + '</div>';
5298 toolbar += ' </li>';
5299 cnt++;
5300 }
5301 break;
5302 case 'SHARE':
5303 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="' + tIcon + '">';
5304 toolbar += ' <div>' + G.O.icons.thumbnailShare + '</div>';
5305 toolbar += ' </li>';
5306 cnt++;
5307 break;
5308 case 'DOWNLOAD':
5309 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="' + tIcon + '">';
5310 toolbar += ' <div>' + G.O.icons.thumbnailDownload + '</div>';
5311 toolbar += ' </li>';
5312 cnt++;
5313 break;
5314 case 'INFO':
5315 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="' + tIcon + '">';
5316 toolbar += ' <div>' + G.O.icons.thumbnailInfo + '</div>';
5317 toolbar += ' </li>';
5318 cnt++;
5319 break;
5320 case 'SHOPPINGCART':
5321 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="' + tIcon + '">';
5322 // toolbar += ' <div>' + G.O.icons.thumbnailShoppingcart + '</div>';
5323 toolbar += ThumbnailBuildToolbarOneCart( item );
5324
5325 toolbar += ' </li>';
5326 cnt++;
5327 break;
5328 case 'DISPLAY':
5329 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="DISPLAY">';
5330 toolbar += ' <div class="nGY2GThumbnailIconImageShare">' + G.O.icons.thumbnailDisplay + '</div>';
5331 toolbar += ' </li>';
5332 cnt++;
5333 break;
5334 case 'CUSTOM1':
5335 case 'CUSTOM2':
5336 case 'CUSTOM3':
5337 case 'CUSTOM4':
5338 case 'CUSTOM5':
5339 case 'CUSTOM6':
5340 case 'CUSTOM7':
5341 case 'CUSTOM8':
5342 case 'CUSTOM9':
5343 case 'CUSTOM10':
5344 var cust = tIcon.replace('CUSTOM', '');
5345 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="' + tIcon.toLowerCase() + '">';
5346 toolbar += ' <div class="nGY2GThumbnailIconImageShare">' + G.O.icons['thumbnailCustomTool' + cust] + '</div>';
5347 toolbar += ' </li>';
5348 cnt++;
5349 break;
5350 case 'FEATURED':
5351 if( item.featured === true ) {
5352 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="">';
5353 toolbar += ' <div class="nGY2GThumbnailIconImageFeatured">' + G.O.icons.thumbnailFeatured + '</div>';
5354 toolbar += ' </li>';
5355 cnt++;
5356 }
5357 break;
5358 case 'SELECT':
5359 if( G.O.thumbnailSelectable == true ) {
5360 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="TOGGLESELECT">';
5361 if( item.selected === true ) {
5362 toolbar += ' <div class="nGY2GThumbnailIconImageSelect nGY2ThumbnailSelected">' + G.O.icons.thumbnailSelected + '</div>';
5363 }
5364 else {
5365 toolbar += ' <div class="nGY2GThumbnailIconImageSelect nGY2ThumbnailUnselected">' + G.O.icons.thumbnailUnselected + '</div>';
5366 }
5367 toolbar += ' </li>';
5368 cnt++;
5369 }
5370 break;
5371 }
5372 }
5373 }
5374 toolbar += ' </ul>';
5375 }
5376
5377 if( cnt > 0 ) {
5378 return toolbar;
5379 }
5380 else {
5381 return '';
5382 }
5383 }
5384
5385 // CART ICON AND COUNTER
5386 function ThumbnailBuildToolbarOneCart( item ) {
5387 var q = 0;
5388
5389 var id = item.GetID()
5390 for( var i=0; i<G.shoppingCart.length; i++ ) {
5391 if( G.I[G.shoppingCart[i].idx].GetID() == id ) {
5392 q = G.shoppingCart[i].qty;
5393 }
5394 }
5395 if( q == 0 ) {
5396 q = '';
5397 }
5398
5399 return ' <div>' + G.O.icons.thumbnailShoppingcart + q + '</div>';
5400 }
5401 function ThumbnailBuildToolbarOneCartUpdate( item ) {
5402 var $e = item.$elt;
5403
5404 if( $e != null ) {
5405 var $q = $e.find('*[data-ngy2action="SHOPPINGCART"]');
5406 if( $q !== undefined ) {
5407 $q.html( ThumbnailBuildToolbarOneCart( item ) );
5408 }
5409 }
5410 }
5411
5412 function getThumbnailTitle( item ) {
5413
5414 var sTitle = item.title;
5415 if( G.O.thumbnailLabel.get('display') == true ) {
5416 if( sTitle === undefined || sTitle.length == 0 ) { sTitle = '&nbsp;'; }
5417
5418 if( G.i18nTranslations.thumbnailImageTitle != '' ) {
5419 sTitle = G.i18nTranslations.thumbnailImageTitle;
5420 }
5421 var ml = G.O.thumbnailLabel.get('titleMaxLength');
5422 if( ml > 3 && sTitle.length > ml ){
5423 sTitle = sTitle.substring(0, ml) + '...';
5424 }
5425 }
5426
5427 return sTitle;
5428 }
5429
5430 function getTumbnailDescription( item ) {
5431 var sDesc = '';
5432 if( G.O.thumbnailLabel.get('displayDescription') == true ) {
5433 if( item.kind == 'album' ) {
5434 if( G.i18nTranslations.thumbnailImageDescription != '' ) {
5435 sDesc = G.i18nTranslations.thumbnailAlbumDescription;
5436 }
5437 else {
5438 sDesc = item.description;
5439 }
5440 }
5441 else {
5442 if( G.i18nTranslations.thumbnailImageDescription != '' ) {
5443 sDesc = G.i18nTranslations.thumbnailImageDescription;
5444 }
5445 else {
5446 sDesc = item.description;
5447 }
5448 }
5449 var ml = G.O.thumbnailLabel.get('descriptionMaxLength');
5450 if( ml > 3 && sDesc.length > ml ){
5451 sDesc = sDesc.substring(0, ml) + '...';
5452 }
5453 if( sDesc.length == 0 ) {
5454 sDesc = '&nbsp;';
5455 }
5456 }
5457
5458 return sDesc;
5459 }
5460
5461
5462
5463 // Retrieve the maximum number of thumbnails that fits in one row
5464 function NbThumbnailsPerRow( areaWidth ) {
5465 var tnW = G.tn.defaultSize.getOuterWidth();
5466
5467 var nbMaxTn = 0;
5468 if( G.O.thumbnailAlignment == 'justified' ) {
5469 nbMaxTn = Math.floor((areaWidth)/(tnW));
5470 }
5471 else {
5472 nbMaxTn = Math.floor((areaWidth + G.tn.settings.GetResponsive('gutterWidth'))/(tnW + G.tn.settings.GetResponsive('gutterWidth')));
5473 }
5474
5475 if( G.O.maxItemsPerLine >0 && nbMaxTn > G.O.maxItemsPerLine ) {
5476 nbMaxTn = G.O.maxItemsPerLine;
5477 }
5478
5479 if( nbMaxTn < 1 ) { nbMaxTn = 1; }
5480
5481 return nbMaxTn
5482 }
5483
5484 // Thumbnail display animation
5485 function ThumbnailAppear( n, cnt ) {
5486 var curTn = G.GOM.items[n];
5487 var item = G.I[curTn.thumbnailIdx];
5488
5489
5490 if( G.tn.opt.Get('displayTransition') == 'NONE' ) {
5491 item.$elt.css({ opacity: 1 });
5492 ThumbnailAppearFinish( item );
5493 }
5494 else {
5495 if( item.$elt == null ) { return; }
5496 var top = G.GOM.cache.containerOffset.top + ( curTn.top - G.GOM.clipArea.top );
5497 var vp = G.GOM.cache.viewport;
5498 if( (top + (curTn.top - G.GOM.clipArea.top)) >= (vp.t - 50) && top <= (vp.t + vp.h + 50) ) {
5499 // display animation only if in the current viewport
5500 var delay = cnt * G.tn.opt.Get('displayInterval');
5501 if( G.tn.opt.Get('displayTransition') == 'CUSTOM' ) {
5502 if( G.GOM.curNavLevel == 'lN' ) {
5503 G.O.fnThumbnailDisplayEffect(item.$elt, item, n, delay);
5504 }
5505 else {
5506 G.O.fnThumbnailL1DisplayEffect(item.$elt, item, n, delay);
5507 }
5508 }
5509 else {
5510 G.GOM.thumbnails2Display.push({itm: item, d: delay});
5511 // ThumbnailDisplayAnim2(item, delay);
5512 }
5513 return;
5514 }
5515 else {
5516 item.$elt.css({ opacity: 1 });
5517 ThumbnailAppearFinish(item);
5518 }
5519 }
5520 }
5521
5522
5523 // displays thumbnail stacks at the end of the display animation
5524 function ThumbnailAppearFinish( item ) {
5525
5526 // add stacks
5527 var ns = G.tn.opt.Get('stacks');
5528 if( ns > 0 ) {
5529 // display stacks
5530 item.$elt.css({ display: 'block'});
5531 var o = 0.9;
5532 // set stack opacity
5533 for( var i = ns-1; i>=0; i-- ) {
5534 item.$elt.eq(i).css('opacity', o);
5535 o = o - 0.2;
5536 }
5537
5538 }
5539 }
5540
5541
5542 function ThumbnailDisplayAnim2( item, delay ) {
5543 function randomIntFromInterval(min,max) {
5544 return Math.floor(Math.random()*(max-min+1)+min);
5545 }
5546 var oFrom = {};
5547 var oTo = {};
5548
5549 switch (G.tn.opt.Get('displayTransition')) {
5550 case 'RANDOMSCALE': {
5551 var scales = [0.95, 1, 1.05, 1.1];
5552 var zi = [1, 2, 3, 4];
5553
5554 var r = randomIntFromInterval(0,3);
5555 while( r == G.GOM.lastRandomValue ) {
5556 r = randomIntFromInterval(0,3);
5557 }
5558 G.GOM.lastRandomValue = r;
5559 let f = scales[r];
5560 // item.$elt.css({ 'z-index': G.GOM.lastZIndex+zi[r], 'box-shadow': '-1px 2px 5px 1px rgba(0, 0, 0, 0.7)' });
5561 item.$elt.css({ 'z-index': G.GOM.lastZIndex+zi[r], 'box-shadow': '0px 0px 5px 3px rgba(0,0,0,0.74)' });
5562
5563 oFrom = { scale: 0.5, opacity:0 };
5564 oTo = { scale: f, opacity:1 };
5565 break;
5566 }
5567
5568 case 'SCALEUP': {
5569 let f = G.tn.opt.Get('displayTransitionStartVal');
5570 if( f == 0 ) { f = 0.6; } // default value
5571 oFrom = { scale: f, opacity: 0 };
5572 oTo = { scale: 1, opacity: 1 };
5573 break;
5574 }
5575
5576 case 'SCALEDOWN': {
5577 let f = G.tn.opt.Get('displayTransitionStartVal');
5578 if( f == 0 ) { f=1.3; } // default value
5579 oFrom = { scale: f, opacity: 0 };
5580 oTo = { scale: 1, opacity: 1 };
5581 break;
5582 }
5583 case 'SLIDEUP': {
5584 let f = G.tn.opt.Get('displayTransitionStartVal');
5585 if( f == 0 ) { f=50; } // default value
5586 oFrom = { opacity: 0, translateY: f };
5587 oTo = { opacity: 1, translateY: 0 };
5588 break;
5589 }
5590 case 'SLIDEDOWN': {
5591 let f=G.tn.opt.Get('displayTransitionStartVal');
5592 if( f == 0 ) { f=-50; } // default value
5593 oFrom = { opacity: 0, translateY: f };
5594 oTo = { opacity: 1, translateY: 0 };
5595 break;
5596 }
5597 case 'FLIPUP': {
5598 let f=G.tn.opt.Get('displayTransitionStartVal');
5599 if( f == 0 ) { f=100; } // default value
5600 oFrom = { opacity: 0, translateY: f, rotateX: 45 };
5601 oTo = { opacity: 1, translateY: 0, rotateX: 0 };
5602 break;
5603 }
5604 case 'FLIPDOWN': {
5605 let f=G.tn.opt.Get('displayTransitionStartVal');
5606 if( f == 0 ) { f=-100; } // default value
5607 oFrom = { opacity: 0, translateY: f, rotateX: -45 };
5608 oTo = { opacity: 1, translateY: 0, rotateX: 0 };
5609 break;
5610 }
5611 case 'SLIDEUP2': {
5612 let f = G.tn.opt.Get('displayTransitionStartVal');
5613 if( f == 0 ) { f=100; } // default value
5614 oFrom = { opacity: 0, translateY: f, rotateY: 40 };
5615 oTo = { opacity: 1, translateY: 0, rotateY: 0 };
5616 break;
5617 }
5618 case 'IMAGESLIDEUP': {
5619 // let f = G.tn.opt.Get('displayTransitionStartVal');
5620 // if( f == 0 ) { f=100; } // default value
5621 oFrom = { opacity: 0, top: '100%' };
5622 oTo = { opacity: 1, top: '0%' };
5623 break;
5624 }
5625 case 'SLIDEDOWN2': {
5626 let f=G.tn.opt.Get('displayTransitionStartVal');
5627 if( f == 0 ) { f=-100; } // default value
5628 oFrom = { opacity: 0, translateY: f, rotateY: 40 };
5629 oTo = { opacity: 1, translateY: 0, rotateY: 0 };
5630 break;
5631 }
5632 case 'SLIDERIGHT': {
5633 let f=G.tn.opt.Get('displayTransitionStartVal');
5634 if( f == 0 ) { f=-150; } // default value
5635 oFrom = { opacity: 0, translateX: f };
5636 oTo = { opacity: 1, translateX: 0 };
5637 break;
5638 }
5639 case 'SLIDELEFT': {
5640 let f=G.tn.opt.Get('displayTransitionStartVal');
5641 if( f == 0 ) { f=150; } // default value
5642 oFrom = { opacity: 0, translateX: f };
5643 oTo = { opacity: 1, translateX: 0 };
5644 break;
5645 }
5646 case 'FADEIN':
5647 oFrom = { opacity: 0 };
5648 oTo = { opacity: 1 };
5649 break;
5650
5651
5652 }
5653
5654 var tweenable = new NGTweenable();
5655 tweenable.tween({
5656 from: oFrom,
5657 to: oTo,
5658 attachment: { $e:item.$elt, item: item, tw: tweenable },
5659 delay: delay,
5660 duration: G.tn.opt.Get('displayTransitionDuration'),
5661 easing: G.tn.opt.Get('displayTransitionEasing'),
5662 step: function (state, att) {
5663 window.requestAnimationFrame( function() {
5664 if( att.item.$elt === null ) { // the thumbnail may have been destroyed since the start of the animation
5665 att.tw.stop(false);
5666 return;
5667 }
5668 switch (G.tn.opt.Get('displayTransition')) {
5669 case 'RANDOMSCALE':
5670 att.$e.css( G.CSStransformName , 'scale(' + state.scale + ')').css('opacity', state.opacity);
5671 break;
5672 case 'SCALEUP':
5673 att.$e.css( G.CSStransformName , 'scale('+state.scale+')').css('opacity',state.opacity);
5674 break;
5675 case 'SCALEDOWN':
5676 att.item.$elt.last().css('opacity', state.opacity);
5677 att.item.CSSTransformSet('.nGY2GThumbnail', 'scale', state.scale);
5678 att.item.CSSTransformApply('.nGY2GThumbnail');
5679 break;
5680 case 'SLIDEUP':
5681 att.item.$elt.css('opacity', state.opacity);
5682 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px, '+state.translateY + 'px');
5683 att.item.CSSTransformApply('.nGY2GThumbnail');
5684 break;
5685 case 'SLIDEDOWN':
5686 att.item.$elt.css('opacity', state.opacity);
5687 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px,'+state.translateY+'px');
5688 att.item.CSSTransformApply('.nGY2GThumbnail');
5689 break;
5690 case 'FLIPUP':
5691 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px,'+state.translateY+'px');
5692 att.item.CSSTransformSet('.nGY2GThumbnail', 'rotateX', state.rotateX+'deg');
5693 att.item.$elt.css('opacity', state.opacity);
5694 att.item.CSSTransformApply('.nGY2GThumbnail');
5695 break;
5696 case 'FLIPDOWN':
5697 att.item.$elt.css('opacity', state.opacity);
5698 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px,' + state.translateY + 'px');
5699 att.item.CSSTransformSet('.nGY2GThumbnail', 'rotateX', state.rotateX + 'deg');
5700 att.item.CSSTransformApply('.nGY2GThumbnail');
5701 break;
5702 case 'SLIDEUP2':
5703 att.item.$elt.css('opacity', state.opacity);
5704 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px,' + state.translateY + 'px');
5705 att.item.CSSTransformSet('.nGY2GThumbnail', 'rotateY', state.rotateY + 'deg');
5706 att.item.CSSTransformApply('.nGY2GThumbnail');
5707 break;
5708 case 'IMAGESLIDEUP':
5709 att.item.$elt.css('opacity', state.opacity);
5710 att.item.$Elts['.nGY2GThumbnailImage'].css('top', state.top);
5711 break;
5712 case 'SLIDEDOWN2':
5713 att.item.$elt.css('opacity', state.opacity);
5714 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px, ' + state.translateY + 'px');
5715 att.item.CSSTransformSet('.nGY2GThumbnail', 'rotateY', state.rotateY + 'deg');
5716 att.item.CSSTransformApply('.nGY2GThumbnail');
5717 break;
5718 case 'SLIDERIGHT':
5719 att.item.$elt.css('opacity', state.opacity);
5720 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', state.translateX + 'px, 0px');
5721 att.item.CSSTransformApply('.nGY2GThumbnail');
5722 break;
5723 case 'SLIDELEFT':
5724 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', state.translateX + 'px, 0px');
5725 att.item.$elt.css('opacity', state.opacity);
5726 att.item.CSSTransformApply('.nGY2GThumbnail');
5727 break;
5728 case 'FADEIN':
5729 att.$e.css(state);
5730 break;
5731 }
5732 });
5733 // att.$e.css( G.CSStransformName , 'scale('+state.scale+')').css('opacity',state.opacity);
5734 },
5735 finish: function (state, att) {
5736 window.requestAnimationFrame( function() {
5737 if( att.item.$elt === null ) { return; }
5738
5739 switch (G.tn.opt.Get('displayTransition')) {
5740 case 'RANDOMSCALE':
5741 att.$e.css( G.CSStransformName , 'scale('+state.scale+')').css('opacity', '');
5742 break;
5743 case 'SCALEUP':
5744 att.$e.css( G.CSStransformName , '').css('opacity', '');
5745 break;
5746 case 'SCALEDOWN':
5747 att.item.$elt.last().css('opacity', '');
5748 att.item.CSSTransformSet('.nGY2GThumbnail', 'scale', state.scale);
5749 att.item.CSSTransformApply('.nGY2GThumbnail');
5750 break;
5751 case 'IMAGESLIDEUP':
5752 att.item.$elt.css('opacity', '');
5753 att.item.$Elts['.nGY2GThumbnailImage'].css('top', 0);
5754 break;
5755 case 'SLIDEDOWN2':
5756 att.item.$elt.css('opacity', '');
5757 att.item.CSSTransformApply('.nGY2GThumbnail');
5758 break;
5759 default :
5760 // case 'SLIDEUP':
5761 // case 'SLIDEDOWN':
5762 // case 'FLIPUP':
5763 // case 'FLIPDOWN':
5764 // case 'SLIDEUP2':
5765 // case 'SLIDERIGHT':
5766 // case 'SLIDELEFT':
5767 // case 'FADEIN':
5768 att.item.$elt.css('opacity', '');
5769 }
5770 ThumbnailAppearFinish(att.item);
5771 });
5772
5773 }
5774 });
5775
5776 }
5777
5778 // batch display thumbnails with animation
5779 function ThumbnailDisplayAnimBatch() {
5780
5781 G.GOM.thumbnails2Display.forEach( function(one) {
5782 ThumbnailDisplayAnim2(one.itm, one.d);
5783 });
5784 G.GOM.thumbnails2Display=[];
5785 }
5786
5787
5788
5789 // ######################################
5790 // Gallery display animation
5791 function GalleryAppear() {
5792
5793 var d=G.galleryDisplayTransitionDuration.Get();
5794 switch( G.galleryDisplayTransition.Get() ){
5795 case 'ROTATEX':
5796 G.$E.base.css({ perspective: '1000px', 'perspective-origin': '50% 0%' });
5797 new NGTweenable().tween({
5798 from: { r: 50 },
5799 to: { r: 0 },
5800 attachment: { orgIdx: G.GOM.albumIdx },
5801 duration: d,
5802 easing: 'easeOutCirc',
5803 step: function (state, att) {
5804 if( att.orgIdx == G.GOM.albumIdx ) {
5805 // window.ng_draf( function() {
5806 G.$E.conTnParent.css( G.CSStransformName , 'rotateX(' + state.r + 'deg)');
5807 // });
5808 }
5809 }
5810 });
5811 break;
5812 case 'SLIDEUP':
5813 G.$E.conTnParent.css({ opacity: 0 });
5814 new NGTweenable().tween({
5815 from: { y: 200, o: 0 },
5816 to: { y: 0, o: 1 },
5817 attachment: { orgIdx: G.GOM.albumIdx },
5818 duration: d,
5819 easing: 'easeOutCirc',
5820 step: function (state, att) {
5821 if( att.orgIdx == G.GOM.albumIdx ) {
5822 // window.ng_draf( function() {
5823 G.$E.conTnParent.css( G.CSStransformName , 'translate( 0px, '+state.y + 'px)').css('opacity', state.o);
5824 // });
5825 }
5826 }
5827 });
5828 break;
5829 case 'NONE':
5830 default:
5831 break;
5832 }
5833
5834
5835 }
5836
5837 // ######################################
5838 // ##### THUMBNAIL HOVER MANAGEMENT #####
5839 // ######################################
5840
5841 function ThumbnailOverInit( GOMidx ) {
5842 // Over init in 2 step:
5843 // 1) init with thumbnailBuildInit2 parameter
5844 // 2) init with the hover effect parameter
5845
5846
5847 var curTn = G.GOM.items[GOMidx];
5848 var item = G.I[curTn.thumbnailIdx];
5849
5850 if( item.$elt == null ) { return; } // zombie
5851
5852 var fu = G.O.fnThumbnailHoverInit;
5853 if( fu !== null ) {
5854 typeof fu == 'function' ? fu($e, item, GOMidx) : window[fu]($e, item, GOMidx);
5855 }
5856
5857 // build initialization
5858 var inits = G.tn.buildInit.get();
5859 for( var j = 0; j < inits.length; j++) {
5860 switch( inits[j].property ) {
5861 // CSS Transform
5862 case 'scale':
5863 case 'rotateX':
5864 case 'rotateY':
5865 case 'rotateZ':
5866 case 'translateX':
5867 case 'translateY':
5868 case 'translateZ':
5869 item.CSSTransformSet(inits[j].element, inits[j].property, inits[j].value);
5870 item.CSSTransformApply(inits[j].element);
5871 break;
5872 // CSS filter
5873 case 'blur':
5874 case 'brightness':
5875 case 'grayscale':
5876 case 'sepia':
5877 case 'contrast':
5878 case 'opacity':
5879 case 'saturate':
5880 item.CSSFilterSet(inits[j].element, inits[j].property, inits[j].value);
5881 item.CSSFilterApply(inits[j].element);
5882 break;
5883 default:
5884 var $t=item.$getElt(inits[j].element);
5885 $t.css( inits[j].property, inits[j].value );
5886 break;
5887 }
5888 }
5889
5890 // hover
5891 var effects = G.tn.hoverEffects.get();
5892 for( var j = 0; j < effects.length; j++) {
5893 if( effects[j].firstKeyframe === true ) {
5894 switch( effects[j].type ) {
5895 case 'scale':
5896 case 'rotateX':
5897 case 'rotateY':
5898 case 'rotateZ':
5899 case 'translateX':
5900 case 'translateY':
5901 case 'translateZ':
5902 item.CSSTransformSet(effects[j].element, effects[j].type, effects[j].from);
5903 item.CSSTransformApply(effects[j].element);
5904 break;
5905 case 'blur':
5906 case 'brightness':
5907 case 'grayscale':
5908 case 'sepia':
5909 case 'contrast':
5910 case 'opacity':
5911 case 'saturate':
5912 item.CSSFilterSet(effects[j].element, effects[j].type, effects[j].from);
5913 item.CSSFilterApply(effects[j].element);
5914 break;
5915 default:
5916 var $t = item.$getElt(effects[j].element);
5917 $t.css( effects[j].type, effects[j].from );
5918 break;
5919
5920 }
5921 }
5922 }
5923 item.hoverInitDone=true;
5924 }
5925
5926 function ThumbnailHoverReInitAll() {
5927 if( G.GOM.albumIdx == -1 ) { return; };
5928 var l = G.GOM.items.length;
5929 for( var i = 0; i < l ; i++ ) {
5930 ThumbnailOverInit(i);
5931 // G.GOM.items[i].hovered=false;
5932 G.I[G.GOM.items[i].thumbnailIdx].hovered = false;
5933 }
5934 }
5935
5936
5937 function ThumbnailHover( GOMidx ) {
5938 if( G.GOM.albumIdx == -1 || !G.galleryResizeEventEnabled ) { return; };
5939 if( G.GOM.slider.hostIdx == GOMidx ) {
5940 // slider hosted on thumbnail -> no hover effect
5941 return;
5942 }
5943 var curTn = G.GOM.items[GOMidx];
5944 var item = G.I[curTn.thumbnailIdx];
5945 if( item.kind == 'albumUp' || item.$elt == null ) { return; }
5946
5947 item.hovered = true;
5948
5949 var fu = G.O.fnThumbnailHover;
5950 if( fu !== null ) {
5951 typeof fu == 'function' ? fu(item.$elt, item, GOMidx) : window[fu](item.$elt, item, GOMidx);
5952 }
5953 var effects = G.tn.hoverEffects.get();
5954
5955 try {
5956 for( var j = 0; j < effects.length; j++) {
5957 if( effects[j].hoverin === true ) {
5958 //item.animate( effects[j], j*10, true );
5959 item.animate( effects[j], 0, true );
5960 }
5961 }
5962 // effects on whole layout
5963 // GalleryResize( GOMidx );
5964 }
5965 catch (e) {
5966 NanoAlert(G, 'error on hover: ' + e.message );
5967 }
5968
5969 }
5970
5971 function ThumbnailHoverOutAll() {
5972 if( G.GOM.albumIdx == -1 ) { return; };
5973 var l = G.GOM.items.length;
5974 for( var i = 0; i < l ; i++ ) {
5975 if( G.GOM.items[i].inDisplayArea ) {
5976 ThumbnailHoverOut(i);
5977 }
5978 else {
5979 G.I[G.GOM.items[i].thumbnailIdx].hovered = false;
5980 }
5981 }
5982 }
5983
5984
5985 function ThumbnailHoverOut( GOMidx ) {
5986 if( G.GOM.albumIdx == -1 || !G.galleryResizeEventEnabled ) { return; }
5987
5988 if( G.GOM.slider.hostIdx == GOMidx ) {
5989 // slider on thumbnail -> no hover effect
5990 return;
5991 }
5992
5993 var curTn = G.GOM.items[GOMidx];
5994 var item = G.I[curTn.thumbnailIdx];
5995 if( item.kind == 'albumUp' || !item.hovered ) { return; }
5996 item.hovered = false;
5997 if( item.$elt == null ) { return; }
5998
5999 var fu = G.O.fnThumbnailHoverOut;
6000 if( fu !== null ) {
6001 typeof fu == 'function' ? fu(item.$elt, item, GOMidx) : window[fu](item.$elt, item, GOMidx);
6002 }
6003
6004 var effects = G.tn.hoverEffects.get();
6005 try {
6006 for( var j = 0; j < effects.length; j++) {
6007 if( effects[j].hoverout === true ) {
6008 // item.animate( effects[j], j*10, false );
6009 item.animate( effects[j], 0, false );
6010 }
6011 }
6012 // effects on whole layout
6013 // GalleryResize( );
6014 }
6015 catch (e) {
6016 NanoAlert(G, 'error on hoverOut: ' + e.message );
6017 }
6018
6019 }
6020
6021
6022 /** @function DisplayPhoto */
6023 function DisplayPhoto( imageID, albumID ) {
6024
6025 if( G.O.debugMode ) { console.log('#DisplayPhoto : '+ albumID +'-'+ imageID); }
6026 var albumIdx = NGY2Item.GetIdx(G, albumID);
6027 if( albumIdx == 0 ) {
6028 G.GOM.curNavLevel = 'l1';
6029 }
6030 else {
6031 G.GOM.curNavLevel = 'lN';
6032 }
6033
6034 if( albumIdx == -1 ) {
6035 // get content of album on root level
6036 if( G.O.kind != '' ) {
6037 // do not add album if Markup or Javascript data
6038 NGY2Item.New( G, '', '', albumID, '0', 'album' ); // create empty album
6039 // albumIdx = G.I.length - 1;
6040 }
6041 }
6042
6043 var ngy2ItemIdx = NGY2Item.GetIdx(G, imageID);
6044 if( ngy2ItemIdx == -1 ) {
6045 // get content of the album
6046 AlbumGetContent( albumID, DisplayPhoto, imageID, albumID );
6047 return;
6048 }
6049
6050 if( G.O.debugMode ) { console.log('#DisplayPhoto : '+ ngy2ItemIdx); }
6051
6052 DisplayPhotoIdx(ngy2ItemIdx);
6053
6054 }
6055
6056 // function AlbumGetContent( albumIdx, fnToCall ) {
6057 function AlbumGetContent( albumID, fnToCall, fnParam1, fnParam2 ) {
6058 // var url='';
6059 // var kind='image';
6060 // var albumIdx=NGY2Item.GetIdx(G, albumID);
6061 // var photoIdx=NGY2Item.GetIdx(G, photoID);
6062
6063 switch( G.O.kind ) {
6064 // MARKUP / API
6065 case '':
6066 AlbumGetMarkupOrApi(fnToCall, fnParam1, fnParam2);
6067 break;
6068 // JSON, Flickr, Picasa, ...
6069 default:
6070 jQuery.nanogallery2['data_'+G.O.kind](G, 'AlbumGetContent', albumID, fnToCall, fnParam1, fnParam2 );
6071 }
6072
6073 }
6074
6075 var mediaList = {
6076 youtube : {
6077 getID: function( url ) {
6078 // https://stackoverflow.com/questions/10591547/how-to-get-youtube-video-id-from-url
6079 var s = url.match( /(?:https?:\/{2})?(?:w{3}\.)?youtu(?:be)?\.(?:com|be)(?:\/watch\?v=|\/)([^\s&]+)/ );
6080 return s != null ? s[1] : null;
6081 },
6082 thumbUrl: function( id ) {
6083 return 'https://img.youtube.com/vi/' + id + '/hqdefault.jpg';
6084 },
6085 url: function( id ) {
6086 return 'https://www.youtube.com/embed/' + id;
6087 },
6088 markup: function( id ) {
6089 // return '<iframe class="nGY2ViewerMedia" src="https://www.youtube.com/embed/' + id + '?rel=0" frameborder="0" gesture="media" allowfullscreen></iframe>';
6090 return '<iframe class="nGY2ViewerMedia" src="https://www.youtube.com/embed/' + id + '?rel=0" frameborder="0" allow="autoplay" allowfullscreen></iframe>';
6091 },
6092 kind: 'iframe'
6093 },
6094 vimeo : {
6095 getID: function( url ) {
6096 // https://stackoverflow.com/questions/2916544/parsing-a-vimeo-id-using-javascript
6097 // var s = url.match( /^.*(vimeo\.com\/)((channels\/[A-z]+\/)|(groups\/[A-z]+\/videos\/))?([0-9]+)/ );
6098 var s = url.match( /(http|https)?:\/\/(www\.)?vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/([^\/]*)\/videos\/|)(\d+)(?:|\/\?)/ );
6099 return s != null ? s[5] : null;
6100 },
6101 url: function( id ) {
6102 return 'https://player.vimeo.com/video/' + id;
6103 },
6104 markup: function( id ) {
6105 // return '<iframe class="nGY2ViewerMedia" src="https://player.vimeo.com/video/' + id + '?rel=0" frameborder="0" gesture="media" allowfullscreen></iframe>';
6106 return '<iframe class="nGY2ViewerMedia" src="https://player.vimeo.com/video/' + id + '?rel=0" frameborder="0" allow="autoplay" allowfullscreen></iframe>';
6107 },
6108 kind: 'iframe'
6109 },
6110 dailymotion : {
6111 getID: function( url ) {
6112 // https://stackoverflow.com/questions/12387389/how-to-parse-dailymotion-video-url-in-javascript
6113 var m = url.match(/^.+dailymotion.com\/(video|hub)\/([^_]+)[^#]*(#video=([^_&]+))?/);
6114 if (m !== null) {
6115 if(m[4] !== undefined) {
6116 return m[4];
6117 }
6118 return m[2];
6119 }
6120 return null;
6121 },
6122 thumbUrl: function( id ) {
6123 return 'https://www.dailymotion.com/thumbnail/video/' + id;
6124 },
6125 url: function( id ) {
6126 return 'https://www.dailymotion.com/embed/video/' + id;
6127 },
6128 markup: function( id ) {
6129 // return '<iframe class="nGY2ViewerMedia" src="https://www.dailymotion.com/embed/video/' + id + '?rel=0" frameborder="0" gesture="media" allowfullscreen></iframe>';
6130 return '<iframe class="nGY2ViewerMedia" src="https://www.dailymotion.com/embed/video/' + id + '?rel=0" frameborder="0" allow="autoplay" allowfullscreen></iframe>';
6131 },
6132 kind: 'iframe'
6133 },
6134 selfhosted : {
6135 // SELF-HOSTED VIDEOS
6136 getID: function( url ) {
6137 // In order to leave things as is, I used ID to identify the extension
6138 // https://stackoverflow.com/questions/6997262/how-to-pull-url-file-extension-out-of-url-string-using-javascript
6139 // Make sure the method used for verifying the extension matches the kind of url your selfhosted video has
6140 var extension = url.split('.').pop();
6141
6142 // supported extensions
6143 var s = ( extension === 'mp4' || extension === 'webm' || extension === 'ogv' || extension === '3gp' ) ? extension : null ;
6144 return s;
6145 },
6146 markup: function( url ) {
6147 // 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>';
6148 var extension = url.split('.').pop();
6149 return '<video controls class="nGY2ViewerMedia"><source src="'+ url +'" type="video/'+ extension +'" preload="auto">Your browser does not support the video tag (HTML 5).</video>';
6150 },
6151 kind: 'video',
6152 selfhosted : true
6153 }
6154 };
6155
6156 function AlbumGetMarkupOrApi ( fnToCall, fnParam1, fnParam2 ) {
6157
6158 if( G.markupOrApiProcessed === true ) {
6159 // already processed (maybe location hash to unknow reference) -> display root album
6160 DisplayAlbum('-1', 0);
6161 return;
6162 }
6163
6164 if( G.O.items !== undefined && G.O.items !== null ) {
6165 // data defined as an object in an option parameter
6166 GetContentApiObject();
6167 }
6168 else {
6169 if( G.O.$markup.length > 0 ) {
6170 // data defined as markup (href elements)
6171 GetContentMarkup( G.O.$markup );
6172 G.O.$markup=[] ;
6173 }
6174 else {
6175 NanoConsoleLog(G, 'error: no media to process.');
6176 return;
6177 }
6178 }
6179
6180 G.markupOrApiProcessed = true;
6181 if( fnToCall !== null && fnToCall !== undefined) {
6182 fnToCall( fnParam1, fnParam2, null );
6183 }
6184 }
6185
6186 function StartsWithProtocol ( path ) {
6187 if( path == undefined ) { return false; }
6188 // if( path == null ) { return false; }
6189
6190 var pattern = /^((http|https|ftp|ftps|file):\/\/)/;
6191 if( !pattern.test(path) ) {
6192 // not a full URL
6193 return false;
6194 }
6195 return true;
6196 }
6197
6198 function GetContentApiObject() {
6199 var foundAlbumID=false;
6200 var nbTitles = 0;
6201 var AlbumPostProcess = NGY2Tools.AlbumPostProcess.bind(G);
6202
6203 G.I[0].contentIsLoaded = true;
6204
6205 jQuery.each(G.O.items, function(i,item){
6206
6207 var title = '';
6208 title = GetI18nItem(item, 'title');
6209 if( title === undefined ) { title=''; }
6210
6211 var src='';
6212 if( item['src'+RetrieveCurWidth().toUpperCase()] !== undefined ) {
6213 src = item['src'+RetrieveCurWidth().toUpperCase()];
6214 }
6215 else {
6216 src = item.src;
6217 }
6218 if( !StartsWithProtocol(src) ) {
6219 src = G.O.itemsBaseURL + src;
6220 }
6221
6222 var thumbsrc = '';
6223 if( item.srct !== undefined && item.srct.length > 0 ) {
6224 thumbsrc = item.srct;
6225 if( !StartsWithProtocol(thumbsrc) ) {
6226 thumbsrc = G.O.itemsBaseURL + thumbsrc;
6227 }
6228 }
6229 else {
6230 thumbsrc = src;
6231 }
6232
6233 if( G.O.thumbnailLabel.get('title') != '' ) {
6234 title = GetImageTitle(src);
6235 }
6236
6237 var description=''; //'&nbsp;';
6238 description=GetI18nItem(item,'description');
6239 if( description === undefined ) { description=''; }
6240 //if( toType(item.description) == 'string' ) {
6241 // description=item.description;
6242 //}
6243
6244 var tags = GetI18nItem(item, 'tags');
6245 if( tags === undefined ) { tags=''; }
6246
6247 var albumID = 0;
6248 if( item.albumID !== undefined ) {
6249 albumID=item.albumID;
6250 foundAlbumID = true;
6251 }
6252 var ID = null;
6253 if( item.ID !== undefined ) {
6254 ID = item.ID;
6255 }
6256 var kind = 'image';
6257 if( item.kind !== undefined && item.kind.length > 0 ) {
6258 kind = item.kind;
6259 }
6260
6261 var newItem=NGY2Item.New( G, title, description, ID, albumID, kind, tags );
6262 if( title != '' ) {
6263 nbTitles++;
6264 }
6265
6266 // media source url - img is the default media kind
6267 newItem.setMediaURL( src, 'img');
6268
6269 // manage media kinds other than IMG
6270 jQuery.each(mediaList, function ( n, media ) {
6271 var id = media.getID(src);
6272 if( id != null ) {
6273 if( typeof media.url == 'function' ) { src = media.url(id); }
6274 if( typeof media.thumbUrl == 'function' ) { thumbsrc = media.thumbUrl(id); }
6275 newItem.mediaKind = media.kind;
6276 newItem.mediaMarkup = ( media.selfhosted ) ? media.markup( src ) : media.markup(id);
6277 return false;
6278 }
6279 });
6280
6281 // image size
6282 if( item.imageWidth !== undefined ) { newItem.imageWidth = item.width; }
6283 if( item.imageHeight !== undefined ) { newItem.imageHeight = item.height; }
6284
6285 // THUMBNAILS
6286
6287 // thumbnail image size
6288 var tw = item.imgtWidth !== undefined ? item.imgtWidth : 0;
6289 var th = item.imgtHeight !== undefined ? item.imgtHeight : 0;
6290
6291 // default thumbnail URL and size
6292 newItem.thumbs = {
6293 url: { l1 : { xs: thumbsrc, sm: thumbsrc, me: thumbsrc, la: thumbsrc, xl: thumbsrc }, lN : { xs: thumbsrc, sm: thumbsrc, me: thumbsrc, la: thumbsrc, xl: thumbsrc } },
6294 width: { l1 : { xs: tw, sm: tw, me: tw, la: tw, xl: tw }, lN : { xs: tw, sm: tw, me: tw, la: tw, xl: tw } },
6295 height: { l1 : { xs: th, sm: th, me: th, la: th, xl: th }, lN : { xs: th, sm: th, me: th, la: th, xl: th } }
6296 };
6297
6298 // default media type -> IMG
6299 if( newItem.mediaKind == 'img' ) {
6300
6301 // responsive thumbnails URL and size
6302 var lst=['xs', 'sm', 'me', 'la', 'xl'];
6303 for( var i=0; i< lst.length; i++ ) {
6304 // url
6305 var turl = item['srct' + lst[i].toUpperCase()];
6306 if( turl !== undefined ) {
6307 if( !StartsWithProtocol(turl) ) {
6308 turl = G.O.itemsBaseURL + turl;
6309 }
6310 newItem.url.l1[lst[i]] = turl;
6311 newItem.url.lN[lst[i]] = turl;
6312 }
6313 // width
6314 var tw = item['imgt' + lst[i].toUpperCase() + 'Width'];
6315 if( tw != undefined ) {
6316 newItem.width.l1[lst[i]] = parseInt(tw);
6317 newItem.width.lN[lst[i]] = parseInt(tw);
6318 }
6319 // height
6320 var th = item['imgt' + lst[i].toUpperCase() + 'Height'];
6321 if( th != undefined ) {
6322 newItem.height.l1[lst[i]] = parseInt(th);
6323 newItem.height.lN[lst[i]] = parseInt(th);
6324 }
6325 }
6326 }
6327
6328 // dominant colors (needs to be a base64 gif)
6329 if( item.imageDominantColors !== undefined ) {
6330 newItem.imageDominantColors = item.imageDominantColors;
6331 }
6332 // dominant color (rgb hex)
6333 if( item.imageDominantColor !== undefined ) {
6334 newItem.imageDominantColor = item.imageDominantColor;
6335 }
6336
6337 // dest url
6338 if( item.destURL !== undefined && item.destURL.length>0 ) {
6339 newItem.destinationURL = item.destURL;
6340 }
6341
6342 // download image url
6343 if( item.downloadURL !== undefined && item.downloadURL.length>0 ) {
6344 newItem.downloadURL = item.downloadURL;
6345 }
6346
6347 // EXIF DATA
6348 // Exif - model
6349 if( item.exifModel !== undefined ) { newItem.exif.model = item.exifModel; }
6350 // Exif - flash
6351 if( item.exifFlash !== undefined ) { newItem.exif.flash = item.exifFlash; }
6352 // Exif - focallength
6353 if( item.exifFocalLength !== undefined ) { newItem.exif.focallength = item.exifFocalLength; }
6354 // Exif - fstop
6355 if( item.exifFStop !== undefined ) { newItem.exif.fstop = item.exifFStop; }
6356 // Exif - exposure
6357 if( item.exifExposure !== undefined ) { newItem.exif.exposure = item.exifExposure; }
6358 // Exif - time
6359 if( item.exifIso !== undefined ) { newItem.exif.iso = item.exifIso; }
6360 // Exif - iso
6361 if( item.exifTime !== undefined ) { newItem.exif.time = item.exifTime; }
6362 // Exif - location
6363 if( item.exifLocation !== undefined ) { newItem.exif.location = item.exifLocation; }
6364
6365
6366 // custom data
6367 if( item.customData !== null ) {
6368 newItem.customData = cloneJSObject( item.customData );
6369 }
6370
6371 newItem.contentIsLoaded = true;
6372
6373 var fu = G.O.fnProcessData;
6374 if( fu !== null ) {
6375 typeof fu == 'function' ? fu(newItem, 'api', item) : window[fu](newItem, 'api', item);
6376 }
6377
6378 AlbumPostProcess(albumID);
6379 });
6380
6381 // if( foundAlbumID ) { G.O.displayBreadcrumb=true; }
6382 if( nbTitles == 0 ) { G.O.thumbnailLabel.display=false; }
6383
6384 }
6385
6386
6387 // Returns the text of the DOM element (without children)
6388 // Because jQuery().text() returns the text of all children
6389 function ElementGetText( element ) {
6390
6391 var text = '';
6392 if( element.childNodes[0] !== undefined ) {
6393 if( element.childNodes[0].nodeValue !== null && element.childNodes[0].nodeValue !== undefined ) {
6394 text = element.childNodes[0].nodeValue.trim();
6395 }
6396 }
6397 return text;
6398 }
6399
6400 // Extract items from the jQuery elements
6401 function GetContentMarkup( $elements, group ) {
6402 var foundAlbumID = false;
6403 var nbTitles = 0;
6404 var AlbumPostProcess = NGY2Tools.AlbumPostProcess.bind(G);
6405 var GetImageTitleFromURL = NGY2Tools.GetImageTitleFromURL.bind(G);
6406
6407 G.I[0].contentIsLoaded = true;
6408
6409 jQuery.each($elements, function(i, item){
6410
6411 // compare to group defined on the element that has been clicked (lightbox standalone)
6412 if( item.dataset.nanogallery2Lgroup != group ) { return; }
6413
6414 // create dictionnary with all data attribute name in lowercase (to be case unsensitive)
6415 var data = {
6416 // all possible data attributes with some default values
6417 'data-ngdesc': '', // item description
6418 'data-ngid': null, // ID
6419 'data-ngkind': 'image', // kind (image, album, albumup)
6420 'data-ngtags': null, // tags
6421 'data-ngdest': '', // destination URL
6422 'data-ngthumbimgwidth': 0, // thumbnail width
6423 'data-ngthumbimgheight': 0, // thumbnail height
6424 'data-ngimagewidth': 0, // image width
6425 'data-ngimageheight': 0, // image height
6426 'data-ngimagedominantcolors': null, // image dominant colors
6427 'data-ngimagedominantcolor': null, // image dominant colors
6428 'data-ngexifmodel': '', // EXIF data
6429 'data-ngexifflash': '',
6430 'data-ngexiffocallength': '',
6431 'data-ngexiffstop': '',
6432 'data-ngexifexposure': '',
6433 'data-ngexifiso': '',
6434 'data-ngexiftime': '',
6435 'data-ngexiflocation': '',
6436 'data-ngsrc': '',
6437 'alt': ''
6438 };
6439
6440 // Extract data attributes from main item
6441 [].forEach.call( item.attributes, function(attr) {
6442 data[attr.name.toLowerCase()] = attr.value.trim();
6443 });
6444
6445 var title = ElementGetText(item);
6446 if( title == '' && data.alt != '') {
6447 // no title -> check ALT attribute of main element
6448 title = data['alt'];
6449 }
6450
6451 // Complete with data attributes from all children
6452 jQuery.each($(item).children(), function(i, sub_item){
6453
6454 // title may be set on a child element
6455 if( title == '' ) {
6456 title = ElementGetText(sub_item);
6457 }
6458
6459 [].forEach.call( sub_item.attributes, function(attr) {
6460 data[attr.name.toLowerCase()] = attr.value.trim();
6461 });
6462
6463 if( title == '' && data.alt != '') {
6464 // no title -> check ALT attribute of sub element
6465 title = data['alt'];
6466 }
6467
6468 });
6469
6470 // BIG IMAGE URL
6471 // responsive image URL
6472 var src = '',
6473 st = RetrieveCurWidth().toUpperCase();
6474 if( data.hasOwnProperty('data-ngsrc'+st) ) {
6475 src = data['data-ngsrc'+st];
6476 }
6477 // image URL from data-ngsrc attribute
6478 // if( src == '' ) {
6479 // src = data['data-ngsrc'];
6480 // }
6481 // image URL from href attribute (a element)
6482 // if( src == '' ) {
6483 // src = data['href'];
6484 // }
6485 src = src || data['data-ngsrc'] || data['href'];
6486 if( !StartsWithProtocol(src) ) { // do not add the base URL if src starts with a protocol (http, https...)
6487 src = G.O.itemsBaseURL + src;
6488 }
6489
6490
6491 // THUMBNAIL IMAGE
6492 var thumbsrc = '';
6493 // src attribute (img element)
6494 if( data.hasOwnProperty('src') ) {
6495 thumbsrc = data['src'];
6496 }
6497 // data-ngthumb attribute
6498 if( thumbsrc == '' && data.hasOwnProperty('data-ngthumb') ) {
6499 thumbsrc = data['data-ngthumb'];
6500 }
6501 if( thumbsrc == '' ) {
6502 thumbsrc = src; // no thumbnail image URL -> use big image URL
6503 }
6504 if( !StartsWithProtocol(thumbsrc) ) {
6505 thumbsrc = G.O.itemsBaseURL + thumbsrc;
6506 }
6507
6508 //newObj.description=jQuery(item).attr('data-ngdesc');
6509 var description = data['data-ngdesc'];
6510 var ID = data['id'] || data['data-ngid'];
6511 // if( ID == undefined ) {
6512 // ID = data['data-ngid'];
6513 // }
6514 var kind = data['data-ngkind'];
6515 var tags = data['data-ngtags'];
6516
6517 var albumID = '0';
6518 if( data.hasOwnProperty('data-ngalbumid') ) {
6519 albumID = data['data-ngalbumid'];
6520 foundAlbumID = true;
6521 }
6522
6523 // var title = jQuery(item).text();
6524 var title_from_url = GetImageTitleFromURL( src );
6525 if( title_from_url != '' ) {
6526 title = title_from_url;
6527 }
6528
6529
6530 var newItem = NGY2Item.New( G, title, description, ID, albumID, kind, tags );
6531 if( title != '' ) {
6532 nbTitles++;
6533 }
6534
6535 // media source url - img is the default media kind
6536 newItem.setMediaURL( src, 'img');
6537
6538 // manage media kinds other than IMG
6539 // newItem.mediaKind = 'img';
6540 jQuery.each(mediaList, function ( n, media ) {
6541 var id = media.getID(src);
6542 if( id != null ) {
6543 if( typeof media.url == 'function' ) { src = media.url(id); }
6544 if( typeof media.thumbUrl == 'function' ) { thumbsrc = media.thumbUrl(id); }
6545 newItem.mediaKind = media.kind;
6546 newItem.mediaMarkup = ( media.selfhosted ) ? media.markup( src ) : media.markup(id);
6547 return false;
6548 }
6549 });
6550
6551
6552 // Big image size
6553 newItem.imageWidth = parseInt( data['data-ngimagewidth'] );
6554 newItem.imageHeight = parseInt( data['data-ngimageheight'] );
6555
6556 // default thumbnail image URL and size
6557 var tw = parseInt(data['data-ngthumbimgwidth']);
6558 var th = parseInt(data['data-ngthumbimgheight']);
6559 newItem.thumbs = {
6560 url: { l1 : { xs: thumbsrc, sm: thumbsrc, me: thumbsrc, la: thumbsrc, xl: thumbsrc }, lN : { xs: thumbsrc, sm: thumbsrc, me: thumbsrc, la: thumbsrc, xl: thumbsrc } },
6561 width: { l1 : { xs: tw, sm: tw, me: tw, la: tw, xl: tw }, lN : { xs: tw, sm: tw, me: tw, la: tw, xl: tw } },
6562 height: { l1 : { xs: th, sm: th, me: th, la: th, xl: th }, lN : { xs: th, sm: th, me: th, la: th, xl: th } }
6563 };
6564
6565 // Media type -> IMG
6566 if( newItem.mediaKind == 'img' ) {
6567
6568 // responsive thumbnails URL and size
6569 var lst = ['xs', 'sm', 'me', 'la', 'xl'];
6570 for( var i = 0; i < lst.length; i++ ) {
6571 // url
6572 if( data.hasOwnProperty('data-ngthumb' + lst[i]) ) {
6573 var turl=data['data-ngthumb' + lst[i]];
6574 if( !StartsWithProtocol(turl) ) {
6575 turl = G.O.itemsBaseURL + turl;
6576 }
6577 newItem.url.l1[lst[i]] = turl;
6578 newItem.url.lN[lst[i]] = turl;
6579 }
6580
6581 // width
6582 if( data.hasOwnProperty('data-ngthumb' + lst[i] + 'width') ) {
6583 var tw=parseInt(data['data-ngthumb' + lst[i] + 'width']);
6584 newItem.width.l1[lst[i]] = tw;
6585 newItem.width.lN[lst[i]] = tw;
6586 }
6587 // height
6588 if( data.hasOwnProperty('data-ngthumb' + lst[i] + 'height') ) {
6589 var th=parseInt('data-ngthumb' + lst[i] + 'height');
6590 newItem.height.l1[lst[i]] = th;
6591 newItem.height.lN[lst[i]] = th;
6592 }
6593 }
6594 }
6595
6596
6597 // dominant colorS (needs to be a base64 gif)
6598 newItem.imageDominantColors = data['data-ngimagedominantcolors'];
6599 // dominant color (rgb hex)
6600 newItem.imageDominantColor = data['data-ngimagedominantcolors'];
6601
6602 newItem.destinationURL = data['data-ngdest'];
6603 newItem.downloadURL = data['data-ngdownloadurl'];
6604
6605 // Exif - model
6606 newItem.exif.model=data['data-ngexifmodel'];
6607 // Exif - flash
6608 newItem.exif.flash=data['data-ngexifflash'];
6609 // Exif - focallength
6610 newItem.exif.focallength=data['data-ngexiffocallength'];
6611 // Exif - fstop
6612 newItem.exif.fstop=data['data-ngexiffstop'];
6613 // Exif - exposure
6614 newItem.exif.exposure=data['data-ngexifexposure'];
6615 // Exif - iso
6616 newItem.exif.iso=data['data-ngexifiso'];
6617 // Exif - time
6618 newItem.exif.time=data['data-ngexiftime'];
6619 // Exif - location
6620 newItem.exif.location=data['data-ngexiflocation'];
6621
6622 newItem.contentIsLoaded = true;
6623
6624 // custom data
6625 if( jQuery(item).data('customdata') !== undefined ) {
6626 newItem.customData = cloneJSObject(jQuery(item).data('customdata'));
6627 }
6628 // custom data
6629 if( jQuery(item).data('ngcustomdata') !== undefined ) {
6630 newItem.customData = cloneJSObject(jQuery(item).data('ngcustomdata'));
6631 }
6632
6633 var fu=G.O.fnProcessData;
6634 if( fu !== null ) {
6635 typeof fu == 'function' ? fu(newItem, 'markup', item) : window[fu](newItem, 'markup', item);
6636 }
6637
6638 AlbumPostProcess(albumID);
6639
6640 });
6641
6642 // if( foundAlbumID ) { G.O.displayBreadcrumb=true; }
6643 if( nbTitles == 0 ) { G.O.thumbnailLabel.display = false; }
6644
6645 }
6646
6647
6648 // ################################
6649 // ##### DEFINE VARIABLES #####
6650 // ################################
6651
6652
6653 /** @function DefineVariables */
6654 function DefineVariables() {
6655
6656 // change 'picasa' to 'google' for compatibility reason
6657 if( G.O.kind.toUpperCase() == 'PICASA' || G.O.kind.toUpperCase() == 'GOOGLE') {
6658 G.O.kind='google2';
6659 }
6660
6661 // management of screen width
6662 G.GOM.cache.viewport = getViewport();
6663 G.GOM.curWidth = RetrieveCurWidth();
6664
6665 // tumbnail toolbar
6666 jQuery.extend(true, G.tn.toolbar.image, G.O.thumbnailToolbarImage );
6667 jQuery.extend(true, G.tn.toolbar.album, G.O.thumbnailToolbarAlbum );
6668 var t = ['image', 'album'];
6669 var pos= ['topLeft', 'topRight', 'bottomLeft', 'bottomRight']
6670 for( var i=0; i < t.length ; i++ ) {
6671 for( var j=0; j < pos.length ; j++ ) {
6672 G.tn.toolbar[t[i]][pos[j]] = G.tn.toolbar[t[i]][pos[j]].toUpperCase();
6673 }
6674 }
6675
6676 // convert label settings
6677 if( G.O.thumbnailLabel.position == 'overImageOnBottom' ) {
6678 G.O.thumbnailLabel.valign = 'bottom';
6679 G.O.thumbnailLabel.position = 'overImage';
6680 }
6681 if( G.O.thumbnailLabel.position == 'overImageOnMiddle' ) {
6682 G.O.thumbnailLabel.valign = 'middle';
6683 G.O.thumbnailLabel.position = 'overImage';
6684 }
6685 if( G.O.thumbnailLabel.position == 'overImageOnTop' ) {
6686 G.O.thumbnailLabel.valign = 'top';
6687 G.O.thumbnailLabel.position = 'overImage';
6688 }
6689 if( G.O.thumbnailL1Label !== undefined && G.O.thumbnailL1Label.position !== undefined ) {
6690 if( G.O.thumbnailL1Label.position == 'overImageOnBottom' ) {
6691 G.O.thumbnailL1Label.valign = 'bottom';
6692 G.O.thumbnailL1Label.position = 'overImage';
6693 }
6694 if( G.O.thumbnailL1Label.position == 'overImageOnMiddle' ) {
6695 G.O.thumbnailL1Label.valign = 'middle';
6696 G.O.thumbnailL1Label.position = 'overImage';
6697 }
6698 if( G.O.thumbnailL1Label.position == 'overImageOnTop' ) {
6699 G.O.thumbnailL1Label.valign = 'top';
6700 G.O.thumbnailL1Label.position = 'overImage';
6701 }
6702 }
6703
6704 // thumbnails label - level dependant settings
6705 G.O.thumbnailLabel.get = function( opt ) {
6706 if( G.GOM.curNavLevel == 'l1' && G.O.thumbnailL1Label !== undefined && G.O.thumbnailL1Label[opt] !== undefined ) {
6707 return G.O.thumbnailL1Label[opt];
6708 }
6709 else {
6710 return G.O.thumbnailLabel[opt];
6711 }
6712 };
6713 G.O.thumbnailLabel.set = function( opt, value ) {
6714 if( G.GOM.curNavLevel == 'l1' && G.O.thumbnailL1Label !== undefined && G.O.thumbnailL1Label[opt] !== undefined ) {
6715 G.O.thumbnailL1Label[opt]=value;
6716 }
6717 else {
6718 G.O.thumbnailLabel[opt]=value;
6719 }
6720 };
6721
6722 if( G.O.blockList != '' ) { G.blockList = G.O.blockList.toUpperCase().split('|'); }
6723 if( G.O.allowList != '' ) { G.allowList = G.O.allowList.toUpperCase().split('|'); }
6724
6725 if( G.O.albumList2 !== undefined && G.O.albumList2 !== null && G.O.albumList2.constructor === Array ) {
6726 var l=G.O.albumList2.length;
6727 for(var i=0; i< l; i++ ) {
6728 G.albumList.push(G.O.albumList2[i]);
6729 }
6730 // G.albumList=G.O.albumList.toUpperCase().split('|');
6731 }
6732 if( G.O.albumList2 !== undefined && typeof G.O.albumList2 == 'string' ) {
6733 G.albumList.push(G.O.albumList2);
6734 }
6735
6736
6737 // thumbnail image crop
6738 G.tn.opt.lN.crop = G.O.thumbnailCrop;
6739 G.tn.opt.l1.crop = G.O.thumbnailL1Crop != null ? G.O.thumbnailL1Crop : G.O.thumbnailCrop;
6740
6741
6742 function ThumbnailOpt( lN, l1, opt) {
6743 G.tn.opt.lN[opt] = G.O[lN];
6744 G.tn.opt.l1[opt] = G.O[lN];
6745 if( toType(G.O[l1]) == 'number' ) {
6746 G.tn.opt.l1[opt] = G.O[l1];
6747 }
6748 }
6749 // thumbnail stacks
6750 ThumbnailOpt('thumbnailStacks', 'thumbnailL1Stacks', 'stacks');
6751 // thumbnail stacks translate X
6752 ThumbnailOpt('thumbnailStacksTranslateX', 'thumbnailL1StacksTranslateX', 'stacksTranslateX');
6753 // thumbnail stacks translate Y
6754 ThumbnailOpt('thumbnailStacksTranslateY', 'thumbnailL1StacksTranslateY', 'stacksTranslateY');
6755 // thumbnail stacks translate Z
6756 ThumbnailOpt('thumbnailStacksTranslateZ', 'thumbnailL1StacksTranslateZ', 'stacksTranslateZ');
6757 // thumbnail stacks rotate X
6758 ThumbnailOpt('thumbnailStacksRotateX', 'thumbnailL1StacksRotateX', 'stacksRotateX');
6759 // thumbnail stacks rotate Y
6760 ThumbnailOpt('thumbnailStacksRotateY', 'thumbnailL1StacksRotateY', 'stacksRotateY');
6761 // thumbnail stacks rotate Z
6762 ThumbnailOpt('thumbnailStacksRotateZ', 'thumbnailL1StacksRotateZ', 'stacksRotateZ');
6763 // thumbnail stacks scale
6764 ThumbnailOpt('thumbnailStacksScale', 'thumbnailL1StacksScale', 'stacksScale');
6765 // thumbnail gutter width
6766 // ThumbnailOpt('thumbnailGutterWidth', 'thumbnailL1GutterWidth', 'gutterWidth');
6767 // thumbnail gutter height
6768 // ThumbnailOpt('thumbnailGutterHeight', 'thumbnailL1GutterHeight', 'gutterHeight');
6769 // thumbnail border horizontal
6770 ThumbnailOpt('thumbnailBorderHorizontal', 'thumbnailL1BorderHorizontal', 'borderHorizontal');
6771 // thumbnail border vertical
6772 ThumbnailOpt('thumbnailBorderVertical', 'thumbnailL1BorderVertical', 'borderVertical');
6773 // thumbnail grid base height (for cascading layout)
6774 ThumbnailOpt('thumbnailBaseGridHeight', 'thumbnailL1BaseGridHeight', 'baseGridHeight');
6775
6776
6777 // Set same value to all widths
6778 function ResponsiveSetSize( setting, level, v ) {
6779 G.tn.settings[setting][level]['xs'] = v;
6780 G.tn.settings[setting][level]['sm'] = v;
6781 G.tn.settings[setting][level]['me'] = v;
6782 G.tn.settings[setting][level]['la'] = v;
6783 G.tn.settings[setting][level]['xl'] = v;
6784 }
6785
6786 // Get and evaluate responsive values from one option
6787 // Responsive is with syntax: n XSn1 SMn2 MEn3 LAn4 XLn5 (where n is the default value)
6788 // Value 'auto' is accepted for all options, but is handeld only for thumbnail width/height
6789 function ResponsiveOption( option, setting, level ) {
6790 var v = G.O[option];
6791
6792 if( v === undefined || v === null ) { return; }
6793
6794 // if( toType(v) == 'number' ) {
6795 if( toType(v) == 'number' || v.indexOf(' ') == -1 ) {
6796 // set value for all widths
6797 var vn = 'auto';
6798 if( v != 'auto' ) { vn = parseInt(v); }
6799 ResponsiveSetSize( setting, level, vn );
6800 }
6801 else {
6802 var sp = v.split(' ');
6803 if( sp.length > 0 && +sp[0] === +sp[0] ) { // check if sp[0] is a number
6804 // first value is the default size for all widths
6805 var vn = 'auto';
6806 if( sp[0] != 'auto' ) { vn = parseInt(sp[0]); }
6807 ResponsiveSetSize( setting, level, vn );
6808 }
6809 for( var i = 1; i < sp.length; i++ ) {
6810 if( /^xs|sm|me|la|xl/i.test( sp[i] ) ) { // regex: i ignores the case and ^ means "starts with"
6811 var wi = sp[i].substring(0, 2).toLowerCase();
6812 var va = sp[i].substring(2);
6813 var vn = 'auto';
6814 if( va != 'auto' ) { vn = parseInt(va); }
6815 G.tn.settings[setting][level][wi] = vn;
6816 }
6817 }
6818 }
6819 }
6820
6821 ResponsiveOption('thumbnailGutterWidth', 'gutterWidth', 'lN');
6822 ResponsiveOption('thumbnailGutterWidth', 'gutterWidth', 'l1'); // set default values for first level
6823 ResponsiveOption('thumbnailL1GutterWidth', 'gutterWidth', 'l1');
6824 ResponsiveOption('thumbnailGutterHeight', 'gutterHeight', 'lN');
6825 ResponsiveOption('thumbnailGutterHeight', 'gutterHeight', 'l1'); // set default values for first level
6826 ResponsiveOption('thumbnailL1GutterHeight', 'gutterHeight', 'l1');
6827
6828 // gallery display mode
6829 G.galleryDisplayMode.lN = G.O.galleryDisplayMode.toUpperCase();
6830 G.galleryDisplayMode.l1 = G.O.galleryL1DisplayMode != null ? G.O.galleryL1DisplayMode.toUpperCase() : G.O.galleryDisplayMode.toUpperCase();
6831
6832 // gallery maximum number of lines of thumbnails
6833 G.galleryMaxRows.lN = G.O.galleryMaxRows;
6834 G.galleryMaxRows.l1 = toType(G.O.galleryL1MaxRows) == 'number' ? G.O.galleryL1MaxRows : G.O.galleryMaxRows;
6835
6836 // gallery last row full
6837 G.galleryLastRowFull.lN = G.O.galleryLastRowFull;
6838 G.galleryLastRowFull.l1 = G.O.galleryL1LastRowFull != null ? G.O.galleryL1LastRowFull : G.O.galleryLastRowFull;
6839
6840 // gallery sorting
6841 G.gallerySorting.lN = G.O.gallerySorting.toUpperCase();
6842 G.gallerySorting.l1 = G.O.galleryL1Sorting != null ? G.O.galleryL1Sorting.toUpperCase() : G.gallerySorting.lN;
6843
6844 // gallery display transition
6845 G.galleryDisplayTransition.lN = G.O.galleryDisplayTransition.toUpperCase();
6846 G.galleryDisplayTransition.l1 = G.O.galleryL1DisplayTransition != null ? G.O.galleryL1DisplayTransition.toUpperCase() : G.galleryDisplayTransition.lN;
6847
6848 // gallery display transition duration
6849 G.galleryDisplayTransitionDuration.lN = G.O.galleryDisplayTransitionDuration;
6850 G.galleryDisplayTransitionDuration.l1 = G.O.galleryL1DisplayTransitionDuration != null ? G.O.galleryL1DisplayTransitionDuration : G.galleryDisplayTransitionDuration.lN;
6851
6852 // gallery max items per album (not for inline/api defined items)
6853 G.galleryMaxItems.lN = G.O.galleryMaxItems;
6854 G.galleryMaxItems.l1 = toType(G.O.galleryL1MaxItems) == 'number' ? G.O.galleryL1MaxItems : G.O.galleryMaxItems;
6855
6856 // gallery filter tags
6857 G.galleryFilterTags.lN = G.O.galleryFilterTags;
6858 G.galleryFilterTags.l1 = G.O.galleryL1FilterTags != null ? G.O.galleryL1FilterTags : G.O.galleryFilterTags;
6859
6860 // gallery filter tags mode
6861 G.galleryFilterTagsMode.lN = G.O.galleryFilterTagsMode;
6862 G.galleryFilterTagsMode.l1 = G.O.galleryL1FilterTagsMode != null ? G.O.galleryL1FilterTagsMode : G.O.galleryFilterTagsMode;
6863
6864 // gallery pagination
6865 G.O.galleryPaginationMode = G.O.galleryPaginationMode.toUpperCase();
6866
6867 if( toType(G.O.slideshowDelay) == 'number' && G.O.slideshowDelay >= 2000 ) {
6868 G.VOM.slideshowDelay = G.O.slideshowDelay;
6869 }
6870 else {
6871 NanoConsoleLog(G, 'Parameter "slideshowDelay" must be an integer >= 2000 ms.');
6872 }
6873
6874 // gallery display transition
6875 if( typeof G.O.thumbnailDisplayTransition == 'boolean' ) {
6876 if( G.O.thumbnailDisplayTransition === true ) {
6877 G.tn.opt.lN.displayTransition = 'FADEIN';
6878 G.tn.opt.l1.displayTransition = 'FADEIN';
6879 }
6880 else {
6881 G.tn.opt.lN.displayTransition = 'NONE';
6882 G.tn.opt.l1.displayTransition = 'NONE';
6883 }
6884 }
6885
6886 if( G.O.fnThumbnailDisplayEffect !== '' ) {
6887 G.tn.opt.lN.displayTransition = 'CUSTOM';
6888 G.tn.opt.l1.displayTransition = 'CUSTOM';
6889 }
6890 if( G.O.fnThumbnailL1DisplayEffect !== '' ) {
6891 G.tn.opt.l1.displayTransition = 'CUSTOM';
6892 }
6893
6894
6895 // thumbnail display transition easing
6896 // set default easing
6897 ThumbnailOpt('thumbnailDisplayTransitionEasing', 'thumbnailL1DisplayTransitionEasing', 'displayTransitionEasing');
6898 // parse thumbnail display transition
6899 function thumbnailDisplayTransitionParse( cfg, level ) {
6900 if( typeof cfg == 'string' ) {
6901 var st=cfg.split('_');
6902 if( st.length == 1 ) {
6903 G.tn.opt[level]['displayTransition'] = cfg.toUpperCase();
6904 }
6905 if( st.length == 2 ) {
6906 G.tn.opt[level]['displayTransition'] = st[0].toUpperCase();
6907 G.tn.opt[level]['displayTransitionStartVal'] = Number(st[1]);
6908 }
6909 if( st.length == 3 ) {
6910 G.tn.opt[level]['displayTransition'] = st[0].toUpperCase();
6911 G.tn.opt[level]['displayTransitionStartVal'] = Number(st[1]);
6912 G.tn.opt[level]['displayTransitionEasing'] = st[2];
6913 }
6914 }
6915 }
6916 thumbnailDisplayTransitionParse( G.O.thumbnailDisplayTransition, 'lN');
6917 thumbnailDisplayTransitionParse( G.O.thumbnailDisplayTransition, 'l1');
6918 thumbnailDisplayTransitionParse( G.O.thumbnailL1DisplayTransition, 'l1');
6919
6920
6921 // thumbnail display transition duration
6922 ThumbnailOpt('thumbnailDisplayTransitionDuration', 'thumbnailL1DisplayTransitionDuration', 'displayTransitionDuration');
6923 // thumbnail display transition interval duration
6924 ThumbnailOpt('thumbnailDisplayInterval', 'thumbnailL1DisplayInterval', 'displayInterval');
6925 // thumbnail display order
6926 ThumbnailOpt('thumbnailDisplayOrder', 'thumbnailL1DisplayOrder', 'displayOrder');
6927
6928
6929 // resolution breakpoints --> convert old syntax to new one
6930 if( G.O.thumbnailSizeSM !== undefined ) { G.O.breakpointSizeSM = G.O.thumbnailSizeSM; }
6931 if( G.O.thumbnailSizeME !== undefined ) { G.O.breakpointSizeME = G.O.thumbnailSizeME; }
6932 if( G.O.thumbnailSizeLA !== undefined ) { G.O.breakpointSizeLA = G.O.thumbnailSizeLA; }
6933 if( G.O.thumbnailSizeXL !== undefined ) { G.O.breakpointSizeXL = G.O.thumbnailSizeXL; }
6934
6935 // THUMBNAIL BUILD INIT
6936 //level 1
6937 if( G.O.thumbnailL1BuildInit2 !== undefined ) {
6938 var t1 = G.O.thumbnailL1BuildInit2.split('|');
6939 for( var i = 0; i < t1.length; i++ ) {
6940 var o1 = t1[i].trim().split('_');
6941 if( o1.length == 3 ) {
6942 var i1 = NewTBuildInit();
6943 i1.element = ThumbnailOverEffectsGetCSSElement(o1[0], '');
6944 i1.property = o1[1];
6945 i1.value = o1[2];
6946 G.tn.buildInit.level1.push(i1);
6947 }
6948 }
6949 }
6950 //level N
6951 if( G.O.thumbnailBuildInit2 !== undefined ) {
6952 var t1 = G.O.thumbnailBuildInit2.split('|');
6953 for( var i = 0; i < t1.length; i++ ) {
6954 var o1 = t1[i].trim().split('_');
6955 if( o1.length == 3 ) {
6956 var i1 = NewTBuildInit();
6957 i1.element = ThumbnailOverEffectsGetCSSElement(o1[0], '');
6958 i1.property = o1[1];
6959 i1.value = o1[2];
6960 G.tn.buildInit.std.push(i1);
6961 }
6962 }
6963 }
6964
6965
6966 // THUMBNAIL HOVER EFFETCS
6967
6968 // thumbnails hover effects - Level1
6969 var tL1HE = G.O.thumbnailL1HoverEffect2;
6970 if( tL1HE !== undefined ) {
6971 switch( toType(tL1HE) ) {
6972 case 'string': {
6973 let tmp = tL1HE.split('|');
6974 for(var i = 0; i < tmp.length; i++) {
6975 let oDef = NewTHoverEffect();
6976 oDef = ThumbnailHoverEffectExtract( tmp[i].trim(), oDef );
6977 if( oDef != null ) {
6978 G.tn.hoverEffects.level1.push(oDef);
6979 }
6980 }
6981 break;
6982 }
6983 case 'object': {
6984 let oDef = NewTHoverEffect();
6985 oDef = jQuery.extend(oDef,tL1HE);
6986 oDef = ThumbnailHoverEffectExtract( oDef.name, oDef );
6987 if( oDef != null ) {
6988 G.tn.hoverEffects.level1.push(oDef);
6989 }
6990 break;
6991 }
6992 case 'array': {
6993 for(var i = 0; i < tL1HE.length; i++) {
6994 let oDef = NewTHoverEffect();
6995 oDef = jQuery.extend(oDef,tL1HE[i]);
6996 oDef = ThumbnailHoverEffectExtract( oDef.name, oDef );
6997 if( oDef != null ) {
6998 G.tn.hoverEffects.level1.push(oDef);
6999 }
7000 }
7001 break;
7002 }
7003 case 'null':
7004 break;
7005 default:
7006 NanoAlert(G, 'incorrect parameter for "thumbnailL1HoverEffect2".');
7007 }
7008 }
7009 G.tn.hoverEffects.level1 = ThumbnailOverEffectsPreset(G.tn.hoverEffects.level1);
7010
7011 // thumbnails hover effects - other levels
7012 var tHE = G.O.thumbnailHoverEffect2;
7013 switch( toType(tHE) ) {
7014 case 'string': {
7015 let tmp = tHE.split('|');
7016 for(var i = 0; i < tmp.length; i++) {
7017 let oDef = NewTHoverEffect();
7018 oDef = ThumbnailHoverEffectExtract( tmp[i].trim(), oDef );
7019 if( oDef != null ) {
7020 G.tn.hoverEffects.std.push(oDef);
7021 }
7022 }
7023 break;
7024 }
7025 case 'object': {
7026 let oDef = NewTHoverEffect();
7027 oDef = jQuery.extend(oDef, tHE);
7028 oDef = ThumbnailHoverEffectExtract( oDef.name, oDef );
7029 if( oDef != null ) {
7030 G.tn.hoverEffects.std.push(oDef);
7031 }
7032 break;
7033 }
7034 case 'array': {
7035 for(var i = 0; i < tHE.length; i++) {
7036 let oDef = NewTHoverEffect();
7037 oDef = jQuery.extend(oDef,tHE[i]);
7038 oDef = ThumbnailHoverEffectExtract( oDef.name, oDef );
7039 if( oDef!= null ) {
7040 G.tn.hoverEffects.std.push(oDef);
7041 }
7042 }
7043 break;
7044 }
7045 case 'null':
7046 break;
7047 default:
7048 NanoAlert(G, 'incorrect parameter for "thumbnailHoverEffect2".');
7049 }
7050 G.tn.hoverEffects.std = ThumbnailOverEffectsPreset(G.tn.hoverEffects.std);
7051
7052
7053 if( G.O.touchAnimationL1 == undefined ) {
7054 G.O.touchAnimationL1 = G.O.touchAnimation;
7055 }
7056
7057 // disable thumbnail touch animation when no hover effect defined
7058 if( G.tn.hoverEffects.std.length == 0 ) {
7059 if( G.tn.hoverEffects.level1.length == 0 ) {
7060 G.O.touchAnimationL1 = false;
7061 }
7062 G.O.touchAnimation = false;
7063 }
7064
7065
7066 // thumbnail sizes
7067 if( G.O.thumbnailHeight == 0 || G.O.thumbnailHeight == '' ) { G.O.thumbnailHeight = 'auto'; }
7068 if( G.O.thumbnailWidth == 0 || G.O.thumbnailWidth == '' ) { G.O.thumbnailWidth = 'auto'; }
7069 if( G.O.thumbnailL1Height == 0 || G.O.thumbnailL1Height == '' ) { G.O.thumbnailL1Height = 'auto'; }
7070 if( G.O.thumbnailL1Width == 0 || G.O.thumbnailL1Width == '' ) { G.O.thumbnailL1Width = 'auto'; }
7071
7072 // RETRIEVE ALL THUMBNAIL SIZES
7073 // ThumbnailSizes( 'thumbnailWidth', false, 'width');
7074 // ThumbnailSizes( 'thumbnailL1Width', true, 'width');
7075 // ThumbnailSizes( 'thumbnailHeight', false, 'height');
7076 // ThumbnailSizes( 'thumbnailL1Height', true, 'height');
7077 ResponsiveOption('thumbnailWidth', 'width', 'lN');
7078 ResponsiveOption('thumbnailWidth', 'width', 'l1');
7079 ResponsiveOption('thumbnailL1Width', 'width', 'l1');
7080 ResponsiveOption('thumbnailHeight', 'height', 'lN');
7081 ResponsiveOption('thumbnailHeight', 'height', 'l1');
7082 ResponsiveOption('thumbnailL1Height', 'height', 'l1');
7083
7084
7085 G.O.thumbnailLabelHeight = parseInt(G.O.thumbnailLabelHeight);
7086
7087
7088 // retrieve all mosaic layout patterns
7089 // default pattern
7090 if( G.O.galleryMosaic != undefined ) {
7091 // clone object
7092 G.tn.settings.mosaic.l1.xs = JSON.parse(JSON.stringify(G.O.galleryMosaic));
7093 G.tn.settings.mosaic.l1.sm = JSON.parse(JSON.stringify(G.O.galleryMosaic));
7094 G.tn.settings.mosaic.l1.me = JSON.parse(JSON.stringify(G.O.galleryMosaic));
7095 G.tn.settings.mosaic.l1.la = JSON.parse(JSON.stringify(G.O.galleryMosaic));
7096 G.tn.settings.mosaic.l1.xl = JSON.parse(JSON.stringify(G.O.galleryMosaic));
7097 G.tn.settings.mosaic.lN.xs = JSON.parse(JSON.stringify(G.O.galleryMosaic));
7098 G.tn.settings.mosaic.lN.sm = JSON.parse(JSON.stringify(G.O.galleryMosaic));
7099 G.tn.settings.mosaic.lN.me = JSON.parse(JSON.stringify(G.O.galleryMosaic));
7100 G.tn.settings.mosaic.lN.la = JSON.parse(JSON.stringify(G.O.galleryMosaic));
7101 G.tn.settings.mosaic.lN.xl = JSON.parse(JSON.stringify(G.O.galleryMosaic));
7102 G.tn.settings.mosaicCalcFactor('l1', 'xs');
7103 G.tn.settings.mosaicCalcFactor('l1', 'sm');
7104 G.tn.settings.mosaicCalcFactor('l1', 'me');
7105 G.tn.settings.mosaicCalcFactor('l1', 'la');
7106 G.tn.settings.mosaicCalcFactor('l1', 'xl');
7107 G.tn.settings.mosaicCalcFactor('lN', 'xs');
7108 G.tn.settings.mosaicCalcFactor('lN', 'sm');
7109 G.tn.settings.mosaicCalcFactor('lN', 'me');
7110 G.tn.settings.mosaicCalcFactor('lN', 'la');
7111 G.tn.settings.mosaicCalcFactor('lN', 'xl');
7112 }
7113 if( G.O.galleryL1Mosaic != undefined ) {
7114 // default L1 pattern
7115 G.tn.settings.mosaic.l1.xs = JSON.parse(JSON.stringify(G.O.galleryL1Mosaic));
7116 G.tn.settings.mosaic.l1.sm = JSON.parse(JSON.stringify(G.O.galleryL1Mosaic));
7117 G.tn.settings.mosaic.l1.me = JSON.parse(JSON.stringify(G.O.galleryL1Mosaic));
7118 G.tn.settings.mosaic.l1.la = JSON.parse(JSON.stringify(G.O.galleryL1Mosaic));
7119 G.tn.settings.mosaic.l1.xl = JSON.parse(JSON.stringify(G.O.galleryL1Mosaic));
7120 G.tn.settings.mosaicCalcFactor('l1', 'xs');
7121 G.tn.settings.mosaicCalcFactor('l1', 'sm');
7122 G.tn.settings.mosaicCalcFactor('l1', 'me');
7123 G.tn.settings.mosaicCalcFactor('l1', 'la');
7124 G.tn.settings.mosaicCalcFactor('l1', 'xl');
7125 }
7126
7127 var lst=['xs','sm','me','la','xl'];
7128 // retrieve responsive mosaic definition for levels l1 & lN
7129 for( var w = 0; w < lst.length; w++ ) {
7130 if( G.O['galleryMosaic' + lst[w].toUpperCase()] != undefined ) {
7131 G.tn.settings.mosaic.lN[lst[w]] = JSON.parse(JSON.stringify( G.O['galleryMosaic' + lst[w].toUpperCase()] ));
7132 G.tn.settings.mosaic.l1[lst[w]] = JSON.parse(JSON.stringify( G.O['galleryMosaic' + lst[w].toUpperCase()] ));
7133 G.tn.settings.mosaicCalcFactor('lN',lst[w]);
7134 G.tn.settings.mosaicCalcFactor('l1', lst[w]);
7135 }
7136 }
7137 // retrieve responsive mosaic definition for level l1
7138 for( var w = 0; w < lst.length; w++ ) {
7139 if( G.O['galleryL1Mosaic' + lst[w].toUpperCase()] != undefined ) {
7140 G.tn.settings.mosaic.l1[lst[w]] = JSON.parse(JSON.stringify( G.O['galleryL1Mosaic' + lst[w].toUpperCase()] ));
7141 G.tn.settings.mosaicCalcFactor('l1', lst[w]);
7142 }
7143 }
7144
7145 G.O.imageTransition = G.O.imageTransition.toUpperCase();
7146
7147 G.layout.SetEngine();
7148
7149 // init plugins
7150 switch( G.O.kind ) {
7151 // MARKUP / API
7152 case '':
7153 break;
7154 // JSON, Flickr, Picasa, ...
7155 default:
7156 jQuery.nanogallery2['data_' + G.O.kind](G, 'Init' );
7157 }
7158
7159 }
7160
7161 // HOVER EFFECTS
7162 function ThumbnailHoverEffectExtract( name, effect) {
7163 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'];
7164
7165 var sp = name.split('_');
7166 if( sp.length >= 4 ) {
7167 // var oDef=NewTHoverEffect();
7168 effect.name = '';
7169 effect.type = sp[1];
7170 effect.from = sp[2];
7171 effect.to = sp[3];
7172 if( sp.length >= 5 ) {
7173 // effect.duration=sp[4];
7174
7175 for( var n = 4; n < sp.length; n++ ) {
7176 var v = sp[n];
7177
7178 // check if an easing name
7179 var foundEasing = false;
7180 for( var e = 0; e < easings.length; e++) {
7181 if( v == easings[e] ) {
7182 foundEasing = true;
7183 effect.easing = v;
7184 break;
7185 }
7186 }
7187 if( foundEasing === true ) {
7188 continue;
7189 }
7190
7191 v = v.toUpperCase();
7192
7193 if( v == 'HOVERIN' ) {
7194 effect.hoverout = false;
7195 continue;
7196 }
7197 if( v == 'HOVEROUT' ) {
7198 effect.hoverin = false;
7199 continue;
7200 }
7201
7202 if( v == 'KEYFRAME' ) {
7203 effect.firstKeyframe = false;
7204 continue;
7205 }
7206
7207 var num = parseInt(v.replace(/[^0-9\.]/g, ''), 10); // extract a number if one exists
7208
7209 if( num > 0 ) {
7210 // the string contains a numbers > 0
7211 if( v.indexOf('DURATION') >= 0 ) {
7212 effect.duration = num;
7213 continue;
7214 }
7215 if( v.indexOf('DURATIONBACK') >= 0 ) {
7216 effect.durationBack = num;
7217 continue;
7218 }
7219 if( v.indexOf('DELAY') >= 0 ) {
7220 effect.delay = num;
7221 continue;
7222 }
7223 if( v.indexOf('DELAYBACK') >= 0 ) {
7224 effect.delayBack = num;
7225 continue;
7226 }
7227
7228 // no parameter name found -> default is duration
7229 effect.duration = num;
7230 }
7231 }
7232 }
7233 effect.element = ThumbnailOverEffectsGetCSSElement(sp[0], effect.type);
7234
7235 }
7236 else {
7237 effect.name = name;
7238 // NanoAlert(G, 'incorrect parameter for "thumbnailHoverEffect": ' + name);
7239 // return null;
7240 }
7241 return effect;
7242 }
7243
7244
7245 function ThumbnailOverEffectsGetCSSElement( element, property ) {
7246
7247 var elts = {
7248 'image': '.nGY2GThumbnailImage',
7249 'thumbnail': '.nGY2GThumbnail',
7250 'label': '.nGY2GThumbnailLabel',
7251 'title': '.nGY2GThumbnailTitle',
7252 'description': '.nGY2GThumbnailDescription',
7253 'tools': '.nGY2GThumbnailIcons',
7254 'customlayer': '.nGY2GThumbnailCustomLayer',
7255 'default': 'nGY2GThumbnailImage'
7256 };
7257 return (elts[element] || elts['default']);
7258
7259
7260 }
7261
7262 // convert preset hover effects (nanoGALLERY) to new ones (nanogallery2)
7263 function ThumbnailOverEffectsPreset( effects ) {
7264
7265 // COMPATIBILITY WITH nanoGALLERY
7266 // OK:
7267 // 'borderLighter', 'borderDarker', 'scale120', 'labelAppear', 'labelAppear75', 'labelOpacity50', 'scaleLabelOverImage'
7268 // 'overScale', 'overScaleOutside', 'descriptionAppear'
7269 // 'slideUp', 'slideDown', 'slideRight', 'slideLeft'
7270 // 'imageScale150', 'imageScaleIn80', 'imageScale150Outside', 'imageSlideUp', 'imageSlideDown', 'imageSlideRight', 'imageSlideLeft'
7271 // 'labelSlideUpTop', 'labelSlideUp', 'labelSlideDown', 'descriptionSlideUp'
7272 // KO:
7273 // 'labelSplit4', 'labelSplitVert', 'labelAppearSplit4', 'labelAppearSplitVert'
7274 // TODO:
7275 // 'rotateCornerBL', 'rotateCornerBR', 'imageSplit4', 'imageSplitVert', 'imageRotateCornerBL', 'imageRotateCornerBR', 'imageFlipHorizontal', 'imageFlipVertical'
7276
7277
7278
7279 var newEffects=[];
7280 for( var i=0; i< effects.length; i++ ) {
7281 switch( effects[i].name.toUpperCase() ) {
7282 case 'BORDERLIGHTER': {
7283 let rgb = ColorHelperToRGB(GalleryThemeGetCurrent().thumbnail.borderColor);
7284 let name = 'thumbnail_borderColor_'+rgb+'_'+ShadeBlendConvert(0.5, rgb );
7285 newEffects.push(ThumbnailHoverEffectExtract(name, effects[i]));
7286 break;
7287 }
7288 case 'BORDERDARKER': {
7289 let rgb = ColorHelperToRGB(GalleryThemeGetCurrent().thumbnail.borderColor);
7290 let name = 'thumbnail_borderColor_'+rgb+'_'+ShadeBlendConvert(-0.5, rgb );
7291 newEffects.push(ThumbnailHoverEffectExtract(name, effects[i]));
7292 break;
7293 }
7294 case 'SCALE120':
7295 newEffects.push(ThumbnailHoverEffectExtract('thumbnail_scale_1.00_1.20', effects[i]));
7296 break;
7297 case 'LABELAPPEAR':
7298 case 'LABELAPPEAR75':
7299 newEffects.push(ThumbnailHoverEffectExtract('label_opacity_0.00_1.00', effects[i]));
7300 break;
7301 case 'TOOLSAPPEAR':
7302 newEffects.push(ThumbnailHoverEffectExtract('tools_opacity_0_1', effects[i]));
7303 break;
7304 case 'TOOLSSLIDEDOWN':
7305 newEffects.push(ThumbnailHoverEffectExtract('tools_translateY_-100%_0%', effects[i]));
7306 break;
7307 case 'TOOLSSLIDEUP':
7308 newEffects.push(ThumbnailHoverEffectExtract('tools_translateY_100%_0%', effects[i]));
7309 break;
7310 case 'LABELOPACITY50':
7311 newEffects.push(ThumbnailHoverEffectExtract('label_opacity_1.00_0.50', effects[i]));
7312 break;
7313 case 'LABELSLIDEUPTOP':
7314 case 'LABELSLIDEUP':
7315 newEffects.push(ThumbnailHoverEffectExtract('label_translateY_100%_0%', effects[i]));
7316 newEffects.push(ThumbnailHoverEffectExtract('label_translateY_100%_0%', effects[i]));
7317 break;
7318 case 'LABELSLIDEDOWN':
7319 newEffects.push(ThumbnailHoverEffectExtract('label_translateY_-100%_0%', effects[i]));
7320 break;
7321 case 'SCALELABELOVERIMAGE':
7322 newEffects.push(ThumbnailHoverEffectExtract('label_scale_0.00_1.00', effects[i]));
7323 var n = cloneJSObject(effects[i]);
7324 newEffects.push(ThumbnailHoverEffectExtract('image_scale_1.00_0.00', n));
7325 break;
7326 case 'OVERSCALE':
7327 case 'OVERSCALEOUTSIDE':
7328 //var name = 'label_scale_0_100';
7329 newEffects.push(ThumbnailHoverEffectExtract('label_scale_2.00_1.00', effects[i]));
7330 var n = cloneJSObject(effects[i]);
7331 newEffects.push(ThumbnailHoverEffectExtract('label_opacity_0.00_1.00', n));
7332 n = cloneJSObject(effects[i]);
7333 newEffects.push(ThumbnailHoverEffectExtract('image_scale_1.00_0.00', n));
7334 n = cloneJSObject(effects[i]);
7335 newEffects.push(ThumbnailHoverEffectExtract('image_opacity_1.00_0.00', n));
7336 break;
7337 case 'DESCRIPTIONAPPEAR':
7338 newEffects.push(ThumbnailHoverEffectExtract('description_opacity_0_1', effects[i]));
7339 break;
7340 case 'SLIDERIGHT':
7341 newEffects.push(ThumbnailHoverEffectExtract('image_translateX_0%_100%', effects[i]));
7342 newEffects.push(ThumbnailHoverEffectExtract('label_translateX_-100%_0%', cloneJSObject(effects[i])));
7343 break;
7344 case 'SLIDELEFT':
7345 newEffects.push(ThumbnailHoverEffectExtract('image_translateX_0%_-100%', effects[i]));
7346 newEffects.push(ThumbnailHoverEffectExtract('label_translateX_100%_0%', cloneJSObject(effects[i])));
7347 break;
7348 case 'SLIDEUP':
7349 newEffects.push(ThumbnailHoverEffectExtract('image_translateY_0%_-100%', effects[i]));
7350 newEffects.push(ThumbnailHoverEffectExtract('label_translateY_100%_0%', cloneJSObject(effects[i])));
7351 break;
7352 case 'SLIDEDOWN':
7353 newEffects.push(ThumbnailHoverEffectExtract('image_translateY_0%_100%', effects[i]));
7354 newEffects.push(ThumbnailHoverEffectExtract('label_translateY_-100%_0%', cloneJSObject(effects[i])));
7355 break;
7356 case 'IMAGESCALE150':
7357 case 'IMAGESCALE150OUTSIDE':
7358 newEffects.push(ThumbnailHoverEffectExtract('image_scale_1.00_1.50', effects[i]));
7359 break;
7360 case 'IMAGESCALEIN80':
7361 newEffects.push(ThumbnailHoverEffectExtract('image_scale_1.20_1.00', effects[i]));
7362 break;
7363 case 'IMAGESLIDERIGHT':
7364 newEffects.push(ThumbnailHoverEffectExtract('image_translateX_0%_100%', effects[i]));
7365 break;
7366 case 'IMAGESLIDELEFT':
7367 newEffects.push(ThumbnailHoverEffectExtract('image_translateX_0%_-100%', effects[i]));
7368 break;
7369 case 'IMAGESLIDEUP':
7370 newEffects.push(ThumbnailHoverEffectExtract('image_translateY_0%_-100%', effects[i]));
7371 break;
7372 case 'IMAGESLIDEDOWN':
7373 newEffects.push(ThumbnailHoverEffectExtract('image_translateY_0%_100%', effects[i]));
7374 break;
7375 case 'LABELSLIDEUPDOWN':
7376 newEffects.push(ThumbnailHoverEffectExtract('label_translateY_0%_100%', effects[i]));
7377 break;
7378 case 'DESCRIPTIONSLIDEUP':
7379 newEffects.push(ThumbnailHoverEffectExtract('description_translateY_110%_0%', effects[i]));
7380 break;
7381
7382 case 'IMAGEBLURON':
7383 newEffects.push(ThumbnailHoverEffectExtract('image_blur_2.00px_0.00px', effects[i]));
7384 break;
7385 case 'IMAGEBLUROFF':
7386 newEffects.push(ThumbnailHoverEffectExtract('image_blur_0.00px_2.00px', effects[i]));
7387 break;
7388 case 'IMAGEGRAYON':
7389 newEffects.push(ThumbnailHoverEffectExtract('image_grayscale_0%_100%', effects[i]));
7390 break;
7391 case 'IMAGEGRAYOFF':
7392 newEffects.push(ThumbnailHoverEffectExtract('image_grayscale_100%_0%', effects[i]));
7393 break;
7394 case 'IMAGESEPIAON':
7395 newEffects.push(ThumbnailHoverEffectExtract('image_sepia_100%_1%', effects[i]));
7396 break;
7397 case 'IMAGESEPIAOFF':
7398 newEffects.push(ThumbnailHoverEffectExtract('image_sepia_1%_100%', effects[i]));
7399 break;
7400
7401 default:
7402 newEffects.push(effects[i]);
7403 break;
7404 }
7405 }
7406
7407 return newEffects;
7408 }
7409
7410
7411 // Thumbnail hover effect definition
7412 function NewTHoverEffect() {
7413 var oDef={
7414 name: '',
7415 element: '', // element class
7416 type: '',
7417 from: '', // start value
7418 to: '', // end value
7419 hoverin: true,
7420 hoverout: true,
7421 firstKeyframe: true,
7422 delay: 0,
7423 delayBack: 0,
7424 duration: 400,
7425 durationBack: 300,
7426 easing: 'easeOutQuart',
7427 easingBack: 'easeOutQuart',
7428 animParam: null
7429 };
7430 return oDef;
7431 }
7432
7433 function NewTBuildInit() {
7434 // to set CSS properties
7435 var oDef={ element: '', property: '', value: '' };
7436 return oDef;
7437 }
7438
7439
7440 function ThumbnailStyle( cfg, level) {
7441
7442 switch( cfg.position ){
7443 case 'onBottom' :
7444 G.tn.style[level]['label'] = 'bottom:0; ';
7445 break;
7446 case 'right' :
7447 switch( cfg.valign ) {
7448 case 'top':
7449 G.tn.style[level]['label'] = 'top:0; position:absolute; left: 50%;';
7450 break;
7451 case 'middle':
7452 G.tn.style[level]['label'] = 'top:0; bottom:0; left: 50%;';
7453 G.tn.style[level]['title'] = 'position:absolute; bottom:50%;';
7454 G.tn.style[level]['desc'] = 'position:absolute; top:50%;';
7455 break;
7456 case 'bottom':
7457 default:
7458 G.tn.style[level].label = 'bottom:0; position:absolute; left: 50%;';
7459 G.tn.style[level].title = 'position:absolute;bottom:0;';
7460 break;
7461 }
7462 break;
7463 case 'custom':
7464 break;
7465 default:
7466 case 'overImage' :
7467 switch( cfg.valign ) {
7468 case 'top':
7469 G.tn.style[level]['label'] = 'top:0; position:absolute;';
7470 break;
7471 case 'middle':
7472 G.tn.style[level]['label'] = 'top:0; bottom:0;';
7473 G.tn.style[level]['title'] = 'position:absolute; bottom:50%;';
7474 G.tn.style[level]['desc'] = 'position:absolute; top:50%;';
7475 break;
7476 case 'bottom':
7477 default:
7478 // G.O.thumbnailLabel.position = 'overImageOnBottom';
7479 G.tn.style[level].label = 'bottom:0; position:absolute;';
7480 break;
7481 }
7482
7483 // case 'overImageOnTop' :
7484 // G.tn.style[level]['label'] = 'top:0; position:absolute;';
7485 // break;
7486 // case 'overImageOnMiddle' :
7487 // G.tn.style[level]['label'] = 'top:0; bottom:0;';
7488 // G.tn.style[level]['title'] = 'position:absolute; bottom:50%;';
7489 // G.tn.style[level]['desc'] = 'position:absolute; top:50%;';
7490 // break;
7491 // case 'right' :
7492 // case 'custom' :
7493 // break;
7494 // case 'overImageOnBottom' :
7495 // default :
7496 // G.O.thumbnailLabel.position = 'overImageOnBottom';
7497 // G.tn.style[level].label = 'bottom:0; position:absolute;';
7498 // break;
7499 }
7500
7501 // if( G.layout.engine != 'CASCADING' ) {
7502 if( cfg.position != 'onBottom' ) {
7503 // multi-line
7504 if( cfg.titleMultiLine ) {
7505 G.tn.style[level]['title'] += 'white-space:normal;';
7506 }
7507 if( cfg.descriptionMultiLine ) {
7508 G.tn.style[level]['desc'] += 'white-space:normal;';
7509 }
7510 }
7511
7512 // horizontal alignement
7513 switch( cfg.align ) {
7514 case 'right':
7515 G.tn.style[level].label += 'text-align:right;';
7516 break;
7517 case 'left':
7518 G.tn.style[level].label += 'text-align:left;';
7519 break;
7520 default:
7521 G.tn.style[level].label += 'text-align:center;';
7522 break;
7523 }
7524
7525
7526 if( cfg.titleFontSize != undefined && cfg.titleFontSize != '' ) {
7527 G.tn.style[level].title += 'font-size:' + cfg.titleFontSize + ';';
7528 }
7529 if( cfg.descriptionFontSize != undefined && cfg.descriptionFontSize != '' ) {
7530 G.tn.style[level].desc += 'font-size:' + cfg.descriptionFontSize + ';';
7531 }
7532
7533 if( cfg.displayDescription == false ) {
7534 G.tn.style[level].desc += 'display:none;';
7535 }
7536 }
7537
7538
7539 // cache some thumbnail settings
7540 function ThumbnailDefCaches() {
7541 // thumbnail content CSS styles
7542
7543 // settings for level L1 and LN
7544 ThumbnailStyle( G.O.thumbnailLabel, 'lN');
7545 if( G.O.thumbnailL1Label !== undefined ) {
7546 ThumbnailStyle( G.O.thumbnailL1Label, 'l1');
7547 }
7548 else {
7549 ThumbnailStyle( G.O.thumbnailLabel, 'l1');
7550 }
7551
7552 if( G.O.thumbnailL1Label && G.O.thumbnailL1Label.display ) {
7553 // settings for level L1
7554 ThumbnailStyle( G.O.thumbnailL1Label, 'l1');
7555 }
7556
7557
7558 // default thumbnail sizes levels l1 and lN
7559 var lst=['xs','sm','me','la','xl'];
7560 for( var i = 0; i < lst.length; i++ ) {
7561 var w = G.tn.settings.width.lN[lst[i]];
7562 if( w != 'auto' ) {
7563 G.tn.defaultSize.width.lN[lst[i]] = w;
7564 G.tn.defaultSize.width.l1[lst[i]] = w;
7565 }
7566 else {
7567 var h = G.tn.settings.height.lN[lst[i]];
7568 G.tn.defaultSize.width.lN[lst[i]] = h; // dynamic width --> set height value as default for the width
7569 G.tn.defaultSize.width.l1[lst[i]] = h; // dynamic width --> set height value as default
7570 }
7571 }
7572 for( var i = 0; i < lst.length; i++ ) {
7573 var h = G.tn.settings.height.lN[lst[i]];
7574 if( h != 'auto' ) {
7575 // grid or justified layout
7576 G.tn.defaultSize.height.lN[lst[i]] = h; //+G.tn.labelHeight.get();
7577 G.tn.defaultSize.height.l1[lst[i]] = h; //+G.tn.labelHeight.get();
7578 }
7579 else {
7580 var w = G.tn.settings.width.lN[lst[i]];
7581 G.tn.defaultSize.height.lN[lst[i]] = w; // dynamic height --> set width value as default for the height
7582 G.tn.defaultSize.height.l1[lst[i]] = w; // dynamic height --> set width value as default
7583 }
7584 }
7585
7586 // default thumbnail sizes levels l1
7587 for( var i = 0; i < lst.length; i++ ) {
7588 var w = G.tn.settings.width.l1[lst[i]];
7589 if( w != 'auto' ) {
7590 G.tn.defaultSize.width.l1[lst[i]] = w;
7591 }
7592 else {
7593 var h = G.tn.settings.height.l1[lst[i]];
7594 G.tn.defaultSize.width.l1[lst[i]] = h; // dynamic width --> set height value as default
7595 }
7596 }
7597 for( var i = 0; i < lst.length; i++ ) {
7598 var h = G.tn.settings.height.l1[lst[i]];
7599 if( h != 'auto' ) {
7600 // grid or justified layout
7601 G.tn.defaultSize.height.l1[lst[i]] = h; //+G.tn.labelHeight.get();
7602 }
7603 else {
7604 var w = G.tn.settings.width.l1[lst[i]];
7605 G.tn.defaultSize.height.l1[lst[i]]= w ; // dynamic height --> set width value as default
7606 }
7607 }
7608
7609 }
7610
7611
7612 //
7613 function GalleryThemeGetCurrent() {
7614
7615 var cs=null;
7616 switch(toType(G.O.galleryTheme)) {
7617 case 'object': // user custom color scheme object
7618 cs = G.galleryTheme_dark; // default color scheme
7619 jQuery.extend(true,cs,G.O.galleryTheme);
7620 break;
7621 case 'string': // name of an internal defined color scheme
7622 switch( G.O.galleryTheme ) {
7623 case 'light':
7624 cs = G.galleryTheme_light;
7625 break;
7626 case 'default':
7627 case 'dark':
7628 case 'none':
7629 default:
7630 cs = G.galleryTheme_dark;
7631 }
7632 break;
7633 default:
7634 cs = G.galleryTheme_dark;
7635 }
7636 return cs;
7637 }
7638
7639 // ##### BREADCRUMB/THUMBNAIL COLOR SCHEME #####
7640 function SetGalleryTheme() {
7641
7642 if( typeof G.O.colorScheme !== 'undefined' ) {
7643 G.O.galleryTheme = G.O.colorScheme;
7644 }
7645
7646 var cs = null;
7647 var galleryTheme = '';
7648 switch(toType(G.O.galleryTheme)) {
7649 case 'object': // user custom color scheme object
7650 cs = G.galleryTheme_dark; // default color scheme
7651 jQuery.extend(true,cs,G.O.galleryTheme);
7652 galleryTheme='nanogallery_gallerytheme_custom_' + G.baseEltID;
7653 break;
7654 case 'string': // name of an internal defined color scheme
7655 switch( G.O.galleryTheme ) {
7656 case 'light':
7657 cs = G.galleryTheme_light;
7658 galleryTheme='nanogallery_gallerytheme_light_' + G.baseEltID;
7659 break;
7660 case 'default':
7661 case 'dark':
7662 case 'none':
7663 default:
7664 cs = G.galleryTheme_dark;
7665 galleryTheme='nanogallery_gallerytheme_dark_' + G.baseEltID;
7666 }
7667 break;
7668 default:
7669 NanoAlert(G, 'Error in galleryTheme parameter.');
7670 return;
7671 }
7672
7673 //var s1='.nanogallery_theme_'+G.O.theme+' ';
7674 var s1='.' + galleryTheme + ' ';
7675
7676 // navigation bar
7677 var c = cs.navigationBar;
7678 var s=s1+'.nGY2Navigationbar { background:'+c.background+'; }'+'\n';
7679 if( c.border !== undefined && c.border !== '' ) { s+=s1+'.nGY2Navigationbar { border:'+c.border+'; }'+'\n'; }
7680 if( c.borderTop !== undefined && c.borderTop !== '' ) { s+=s1+'.nGY2Navigationbar { border-top:'+c.borderTop+'; }'+'\n'; }
7681 if( c.borderBottom !== undefined && c.borderBottom !== '' ) { s+=s1+'.nGY2Navigationbar { border-bottom:'+c.borderBottom+'; }'+'\n'; }
7682 if( c.borderRight !== undefined && c.borderRight !== '' ) { s+=s1+'.nGY2Navigationbar { border-right:'+c.borderRight+'; }'+'\n'; }
7683 if( c.borderLeft !== undefined && c.borderLeft !== '' ) { s+=s1+'.nGY2Navigationbar { border-left:'+c.borderLeft+'; }'+'\n'; }
7684
7685 // navigation bar - breadcrumb
7686 var c = cs.navigationBreadcrumb;
7687 s+=s1+'.nGY2Breadcrumb { background:'+c.background+'; border-radius:'+c.borderRadius+'; }'+'\n';
7688 s+=s1+'.nGY2Breadcrumb .oneItem { color:'+c.color+'; }'+'\n';
7689 s+=s1+'.nGY2Breadcrumb .oneItem:hover { color:'+c.colorHover+'; }'+'\n';
7690
7691 // navigation bar - tag filter
7692 var c = cs.navigationFilter;
7693 s+=s1+'.nGY2NavFilterUnselected { color:'+c.color+'; background:'+c.background+'; border-radius:'+c.borderRadius+'; }'+'\n';
7694 s+=s1+'.nGY2NavFilterSelected { color:'+c.colorSelected+'; background:'+c.backgroundSelected+'; border-radius:'+c.borderRadius+'; }'+'\n';
7695 s+=s1+'.nGY2NavFilterSelectAll { color:'+c.colorSelected+'; background:'+c.background+'; border-radius:'+c.borderRadius+'; }'+'\n';
7696
7697 // navigation bar - pagination next/previous
7698 var c = cs.navigationPagination;
7699 s+=s1+'.nGY2NavPagination { color:'+c.color+'; background:'+c.background+'; border-radius:'+c.borderRadius+'; }'+'\n';
7700 s+=s1+'.nGY2NavPagination:hover { color:'+c.colorHover+'; }'+'\n';
7701
7702 // thumbnails
7703 var c = cs.thumbnail;
7704 // 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';
7705 s+=s1+'.nGY2GThumbnail { border-radius: '+c.borderRadius+'; background:'+c.background+'; border-color:'+c.borderColor+'; }'+'\n';
7706 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';
7707 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';
7708 s+=s1+'.nGY2GThumbnailStack { background:'+c.stackBackground+'; }'+'\n';
7709 // s+=s1+'.nGY2GThumbnailImage { background:'+cs.thumbnail.background+'; background-image:'+cs.thumbnail.backgroundImage+'; }'+'\n';
7710 s+=s1+'.nGY2TnImgBack { background:'+c.background+'; background-image:'+c.backgroundImage+'; }'+'\n';
7711 s+=s1+'.nGY2GThumbnailAlbumUp { background:'+c.background+'; background-image:'+c.backgroundImage+'; color:'+cs.thumbnail.titleColor+'; }'+'\n';
7712 s+=s1+'.nGY2GThumbnailIconsFullThumbnail { color:'+c.titleColor+'; }\n';
7713 s+=s1+'.nGY2GThumbnailLabel { background:'+c.labelBackground+'; opacity:'+c.labelOpacity+'; }'+'\n';
7714 s+=s1+'.nGY2GThumbnailImageTitle { color:'+c.titleColor+'; background-color:'+c.titleBgColor+'; '+(c.titleShadow =='' ? '': 'Text-Shadow:'+c.titleShadow+';')+' }'+'\n';
7715 s+=s1+'.nGY2GThumbnailAlbumTitle { color:'+c.titleColor+'; background-color:'+c.titleBgColor+'; '+(c.titleShadow =='' ? '': 'Text-Shadow:'+c.titleShadow+';')+' }'+'\n';
7716 s+=s1+'.nGY2GThumbnailDescription { color:'+c.descriptionColor+'; background-color:'+c.descriptionBgColor+'; '+(c.descriptionShadow =='' ? '': 'Text-Shadow:'+c.descriptionShadow+';')+' }'+'\n';
7717
7718 // thumbnails - icons
7719 var c = cs.thumbnailIcon;
7720 s+=s1+'.nGY2GThumbnailIcons { padding:'+c.padding+'; }\n';
7721 s+=s1+'.nGY2GThumbnailIcon { color:'+c.color+'; '+(c.shadow =='' ? '': 'Text-Shadow:'+c.shadow+';')+' }\n';
7722 s+=s1+'.nGY2GThumbnailIconTextBadge { background-color:'+c.color+'; }\n';
7723
7724 // gallery pagination -> dot/rectangle based
7725 var c = cs.pagination;
7726 if( G.O.galleryPaginationMode != 'NUMBERS' ) {
7727 s+=s1+'.nGY2paginationDot { border:'+c.shapeBorder+'; background:'+c.shapeColor+';}\n';
7728 s+=s1+'.nGY2paginationDotCurrentPage { border:'+c.shapeBorder+'; background:'+c.shapeSelectedColor+';}\n';
7729 s+=s1+'.nGY2paginationRectangle { border:'+c.shapeBorder+'; background:'+c.shapeColor+';}\n';
7730 s+=s1+'.nGY2paginationRectangleCurrentPage { border:'+c.shapeBorder+'; background:'+c.shapeSelectedColor+';}\n';
7731 } else {
7732 s+=s1+'.nGY2paginationItem { background:'+c.background+'; color:'+c.color+'; border-radius:'+c.borderRadius+'; }\n';
7733 s+=s1+'.nGY2paginationItemCurrentPage { background:'+c.background+'; color:'+c.color+'; border-radius:'+c.borderRadius+'; }\n';
7734 s+=s1+'.nGY2PaginationPrev { background:'+c.background+'; color:'+c.color+'; border-radius:'+c.borderRadius+'; }\n';
7735 s+=s1+'.nGY2PaginationNext { background:'+c.background+'; color:'+c.color+'; border-radius:'+c.borderRadius+'; }\n';
7736 s+=s1+'.nGY2paginationItemCurrentPage { background:'+c.backgroundSelected+'; }\n';
7737 }
7738
7739 // gallery more button
7740 var c = cs.thumbnail;
7741 // 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';
7742 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';
7743 s+=s1+'.nGY2GalleryMoreButtonAnnotation { color:'+c.titleColor+'; '+(c.titleShadow =='' ? '': 'Text-Shadow:'+c.titleShadow)+'; }\n';
7744
7745 jQuery('head').append('<style id="ngycs_'+G.baseEltID+'">'+s+'</style>');
7746 G.$E.base.addClass(galleryTheme);
7747
7748 };
7749
7750 // ##### VIEWER COLOR SCHEME #####
7751 function SetViewerTheme( ) {
7752
7753 if( G.VOM.viewerTheme != '' ) {
7754 G.VOM.$baseCont.addClass(G.VOM.viewerTheme);
7755 return;
7756 }
7757
7758 if( typeof G.O.colorSchemeViewer !== 'undefined' ) {
7759 G.O.viewerTheme = G.O.colorSchemeViewer;
7760 }
7761
7762 var cs=null;
7763 switch(toType(G.O.viewerTheme)) {
7764 case 'object': // user custom color scheme object
7765 cs = G.viewerTheme_dark;
7766 jQuery.extend(true, cs, G.O.viewerTheme);
7767 G.VOM.viewerTheme = 'nanogallery_viewertheme_custom_' + G.baseEltID;
7768 break;
7769 case 'string': // name of an internal defined color scheme
7770 switch( G.O.viewerTheme ) {
7771 case 'none':
7772 return;
7773 break;
7774 case 'light':
7775 cs = G.viewerTheme_light;
7776 G.VOM.viewerTheme = 'nanogallery_viewertheme_light_' + G.baseEltID;
7777 break;
7778 case 'dark':
7779 case 'default':
7780 cs = G.viewerTheme_dark;
7781 G.VOM.viewerTheme = 'nanogallery_viewertheme_dark_' + G.baseEltID;
7782 break;
7783 }
7784 break;
7785 default:
7786 NanoAlert(G, 'Error in viewerTheme parameter.');
7787 return;
7788 }
7789
7790 var s1 = '.' + G.VOM.viewerTheme + ' ';
7791 var s = s1 + '.nGY2Viewer { background:' + cs.background + '; }'+'\n';
7792 s += s1 + '.nGY2Viewer .toolbarBackground { background:' + cs.barBackground + '; }'+'\n';
7793 s += s1 + '.nGY2Viewer .toolbar { border:' + cs.barBorder + '; color:' + cs.barColor + '; }'+'\n';
7794 s += s1 + '.nGY2Viewer .toolbar .previousButton:after { color:' + cs.barColor + '; }'+'\n';
7795 s += s1 + '.nGY2Viewer .toolbar .nextButton:after { color:' + cs.barColor + '; }'+'\n';
7796 s += s1 + '.nGY2Viewer .toolbar .closeButton:after { color:' + cs.barColor + '; }'+'\n';
7797 s += s1 + '.nGY2Viewer .toolbar .label .title { color:' + cs.barColor + '; }'+'\n';
7798 s += s1 + '.nGY2Viewer .toolbar .label .description { color:' + cs.barDescriptionColor + '; }'+'\n';
7799 jQuery('head').append('<style>' + s + '</style>');
7800 G.VOM.$baseCont.addClass(G.VOM.viewerTheme);
7801 };
7802
7803
7804
7805 /** @function SetPolyFills */
7806 function SetPolyFills() {
7807
7808 // POLYFILL FOR BIND function --> for older Safari mobile
7809 // found on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind#Compatibility
7810 if (!Function.prototype.bind) {
7811 Function.prototype.bind = function (oThis) {
7812 if (typeof this !== "function") {
7813 // closest thing possible to the ECMAScript 5
7814 // internal IsCallable function
7815 throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
7816 }
7817
7818 var aArgs = Array.prototype.slice.call(arguments, 1),
7819 fToBind = this,
7820 fNOP = function () {},
7821 fBound = function () {
7822 return fToBind.apply(this instanceof fNOP && oThis
7823 ? this
7824 : oThis,
7825 aArgs.concat(Array.prototype.slice.call(arguments)));
7826 };
7827
7828 fNOP.prototype = this.prototype;
7829 fBound.prototype = new fNOP();
7830
7831 return fBound;
7832 };
7833 }
7834
7835 // requestAnimationFrame polyfill by Erik M�ller. fixes from Paul Irish and Tino Zijdel
7836 // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
7837 // http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
7838 // MIT license
7839 (function() {
7840 var lastTime = 0;
7841 var vendors = ['ms', 'moz', 'webkit', 'o'];
7842 for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
7843 window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
7844 window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
7845 }
7846 if (!window.requestAnimationFrame)
7847 window.requestAnimationFrame = function(callback, element) {
7848 var currTime = new Date().getTime();
7849 var timeToCall = Math.max(0, 16 - (currTime - lastTime));
7850 var id = window.setTimeout(function() { callback(currTime + timeToCall); }, timeToCall);
7851 lastTime = currTime + timeToCall;
7852 return id;
7853 };
7854
7855 if (!window.cancelAnimationFrame)
7856 window.cancelAnimationFrame = function(id) {
7857 clearTimeout(id);
7858 };
7859 }());
7860
7861 // array.removeIf -> removes items from array base on a function's result
7862 Array.prototype.removeIf = function(callback) {
7863 var i = this.length;
7864 while (i--) {
7865 if (callback(this[i], i)) {
7866 this.splice(i, 1);
7867 }
7868 }
7869 };
7870
7871 // IE11 for startsWith
7872 // thanks to @lichtamberg - https://github.com/lichtamberg
7873 if (!String.prototype.startsWith) {
7874 String.prototype.startsWith = function(searchString, position) {
7875 position = position || 0;
7876 return this.indexOf(searchString, position) === position;
7877 };
7878 }
7879
7880 }
7881
7882
7883 // Gallery clicked or toolbar touched -> retrieve & execute action
7884 function GalleryClicked(e) {
7885
7886 var r = GalleryEventRetrieveElementl(e, false);
7887
7888 if( r.GOMidx == -1 ) { return 'exit'; }
7889
7890 var idx = G.GOM.items[r.GOMidx].thumbnailIdx;
7891 if( G.GOM.slider.hostIdx == r.GOMidx ) {
7892 idx = G.GOM.items[G.GOM.slider.currentIdx].thumbnailIdx;
7893 }
7894 switch( r.action ) {
7895 case 'OPEN':
7896 ThumbnailOpen(idx, false);
7897 return 'exit';
7898 break;
7899 case 'DISPLAY':
7900 // used the display icon (ignore if selection mode)
7901 ThumbnailOpen(idx, true);
7902 return 'exit';
7903 break;
7904 case 'TOGGLESELECT':
7905 ThumbnailSelectionToggle(idx);
7906 return 'exit';
7907 break;
7908 case 'SHARE':
7909 PopupShare(idx);
7910 return 'exit';
7911 break;
7912 case 'DOWNLOAD':
7913 DownloadImage(idx);
7914 return 'exit';
7915 break;
7916 case 'INFO':
7917 ItemDisplayInfo(G.I[idx]);
7918 return 'exit';
7919 break;
7920 case 'SHOPPINGCART':
7921 AddToCart(idx, 'gallery');
7922 return 'exit';
7923 break;
7924 default:
7925 // all other actions (custom1..10, or anything else)
7926 var fu = G.O.fnThumbnailToolCustAction;
7927 if( fu !== null ) {
7928 typeof fu == 'function' ? fu(r.action, G.I[idx]) : window[fu](r.action, G.I[idx]);
7929 }
7930 break;
7931 }
7932 }
7933
7934 // Download an image
7935 function DownloadImage(idx) {
7936 if( G.I[idx].mediaKind != 'img' ) { return; }
7937
7938
7939 var url = G.I[idx].src;
7940
7941 if( G.I[idx].downloadURL != undefined && G.I[idx].downloadURL != '' ) {
7942 url = G.I[idx].downloadURL;
7943 }
7944
7945 var a = document.createElement('a');
7946 a.href = url;
7947 // a.download = url.split('.').pop();
7948 a.download = url.split('/').pop();
7949 a.target = '_blank';
7950 a.style.display = 'none';
7951 document.body.appendChild(a);
7952 a.click();
7953 document.body.removeChild(a);
7954
7955 }
7956
7957 // add one image to the shopping cart
7958 function AddToCart( idx, source ) {
7959 // increment quantity if already in shopping cart
7960 var found=false;
7961 for( var i=0; i<G.shoppingCart.length; i++ ) {
7962 if( G.shoppingCart[i].idx == idx ) {
7963 G.shoppingCart[i].qty++;
7964 ThumbnailBuildToolbarOneCartUpdate( G.I[idx] );
7965
7966 var fu = G.O.fnShoppingCartUpdated;
7967 if( fu !== null ) {
7968 typeof fu == 'function' ? fu(G.shoppingCart, G.I[idx], source) : window[fu](G.shoppingCart, G.I[idx], source);
7969 }
7970 TriggerCustomEvent('shoppingCartUpdated');
7971 return;
7972 }
7973 }
7974
7975 // add to shopping cart
7976 if( !found) {
7977 G.shoppingCart.push( { idx:idx, ID:G.I[idx].GetID(), qty:1} );
7978 ThumbnailBuildToolbarOneCartUpdate(G.I[idx]);
7979
7980 var fu=G.O.fnShoppingCartUpdated;
7981 if( fu !== null ) {
7982 typeof fu == 'function' ? fu(G.shoppingCart, G.I[idx], source) : window[fu](G.shoppingCart, G.I[idx], source);
7983 }
7984 TriggerCustomEvent('shoppingCartUpdated');
7985 }
7986 }
7987
7988
7989 // All thumbnails are set to unselected
7990 function ThumbnailSelectionClear() {
7991 G.GOM.nbSelected = 0;
7992 for( var i = 0, nbTn = G.GOM.items.length; i < nbTn ; i++ ) {
7993 var item = G .I[G.GOM.items[i].thumbnailIdx];
7994 if( item.selected ) {
7995 item.selected = false;
7996 var fu = G.O.fnThumbnailSelection;
7997 if( fu !== null ) {
7998 typeof fu == 'function' ? fu(item.$elt, item, G.I) : window[fu](item.$elt, item, G.I);
7999 }
8000 }
8001 item.selected = false;
8002 }
8003 }
8004
8005 function ThumbnailSelectionToggle( idx ){
8006 var item = G.I[idx];
8007 if( item.selected === true ) {
8008 ThumbnailSelectionSet(item, false);
8009 G.GOM.nbSelected--;
8010 TriggerCustomEvent('itemUnSelected');
8011 }
8012 else {
8013 ThumbnailSelectionSet(item, true);
8014 G.GOM.nbSelected++;
8015 TriggerCustomEvent('itemSelected');
8016 }
8017 }
8018
8019
8020 // this replaces ThumbnailSelection()
8021 function ThumbnailSelectionSet(item, selected ){
8022
8023 item.selected = selected;
8024
8025 ThumbnailSelectionSetIcon( item );
8026
8027 // called when the selection status of an item changed
8028 var fu=G.O.fnThumbnailSelection;
8029 if( fu !== null ) {
8030 typeof fu == 'function' ? fu(item.$elt, item, G.I) : window[fu](item.$elt, item, G.I);
8031 }
8032
8033 }
8034
8035 function ThumbnailSelectionSetIcon( item ) {
8036 if( item.$elt == null ) {
8037 // thumbnail is not built
8038 return;
8039 }
8040 var $sub = item.$getElt('.nGY2GThumbnail');
8041 var $icon = item.$getElt('.nGY2GThumbnailIconImageSelect');
8042 if( item.selected === true) {
8043 $sub.addClass('nGY2GThumbnailSubSelected');
8044 $icon.addClass('nGY2ThumbnailSelected');
8045 $icon.removeClass('nGY2ThumbnailUnselected');
8046 $icon.html(G.O.icons.thumbnailSelected);
8047 }
8048 else {
8049 $sub.removeClass('nGY2GThumbnailSubSelected');
8050 $icon.removeClass('nGY2ThumbnailSelected');
8051 $icon.addClass('nGY2ThumbnailUnselected');
8052 $icon.html(G.O.icons.thumbnailUnselected);
8053 }
8054 }
8055
8056
8057 // display a modal popup for sharing image/album
8058 function PopupShare(idx) {
8059
8060 // SEE SAMPLES: https://gist.github.com/chrisjlee/5196139
8061 // https://github.com/Julienh/Sharrre
8062
8063 var item=G.I[idx];
8064
8065 var currentURL=document.location.protocol + '//' + document.location.hostname + document.location.pathname;
8066 var newLocationHash = '#nanogallery/' + G.baseEltID + '/';
8067 if( item.kind == 'image' ) {
8068 newLocationHash += item.albumID + '/' + item.GetID();
8069 }
8070 else {
8071 newLocationHash += item.GetID();
8072 }
8073
8074 var content = '<br><br>';
8075 content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="facebook">' + G.O.icons.shareFacebook + '</div>';
8076 content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="pinterest">' + G.O.icons.sharePinterest + '</div>';
8077 content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="tumblr">' + G.O.icons.shareTumblr + '</div>';
8078 content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="twitter">' + G.O.icons.shareTwitter + '</div>';
8079 // content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="googleplus">' + G.O.icons.shareGooglePlus + '</div>';
8080 content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="vk">' + G.O.icons.shareVK + '</div>';
8081 content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="mail">' + G.O.icons.shareMail + '</div>';
8082 content += '<div class="nGY2PopupOneItem" style="text-align:center;"></div>';
8083 content += '<input class="nGY2PopupOneItemText" readonly type="text" value="' + currentURL+newLocationHash + '" style="width:100%;text-align:center;">';
8084 content += '<br>';
8085
8086 currentURL = encodeURIComponent(document.location.protocol + '//' + document.location.hostname + document.location.pathname + newLocationHash);
8087
8088 var currentTitle = item.title;
8089 var currentTn = item.thumbImg().src;
8090
8091
8092 Popup('nanogallery2 - share to:', content, 'Center');
8093
8094 G.popup.$elt.find('.nGY2PopupOneItem').on('click', function(e) {
8095 e.stopPropagation();
8096
8097 var shareURL = '';
8098 var found = true;
8099 switch(jQuery(this).attr('data-share').toUpperCase()) {
8100 case 'FACEBOOK':
8101 // <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>
8102 //window.open("https://www.facebook.com/sharer.php?u="+currentURL,"","height=368,width=600,left=100,top=100,menubar=0");
8103 shareURL = 'https://www.facebook.com/sharer.php?u=' + currentURL;
8104 break;
8105 case 'VK':
8106 shareURL = 'http://vk.com/share.php?url=' + currentURL;
8107 break;
8108 case 'GOOGLEPLUS':
8109 shareURL = "https://plus.google.com/share?url=" + currentURL;
8110 break;
8111 case 'TWITTER':
8112 // shareURL="https://twitter.com/share?url="+currentURL+"&text="+currentTitle;
8113 shareURL = 'https://twitter.com/intent/tweet?text=' + currentTitle + 'url=' + currentURL;
8114 break;
8115 case 'PINTEREST':
8116 // shareURL='https://pinterest.com/pin/create/bookmarklet/?media='+currentTn+'&url='+currentURL+'&description='+currentTitle;
8117 shareURL = 'https://pinterest.com/pin/create/button/?media=' + currentTn + '&url=' + currentURL + '&description=' + currentTitle;
8118 break;
8119 case 'TUMBLR':
8120 //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;
8121 shareURL = 'http://www.tumblr.com/share/link?url=' + currentURL + '&name=' + currentTitle;
8122 break;
8123 case 'MAIL':
8124 shareURL = 'mailto:?subject=' + currentTitle + '&body=' + currentURL;
8125 break;
8126 default:
8127 found = false;
8128 break;
8129 }
8130
8131 if( found ) {
8132 window.open(shareURL, "" , "height=550,width=500,left=100,top=100,menubar=0" );
8133 G.popup.close();
8134 // $popup.remove();
8135 }
8136
8137 });
8138 }
8139
8140 // build a modal popup
8141 function Popup(title, content, align) {
8142 var pp = '<div class="nGY2Popup" style="opacity:0;"><div class="nGY2PopupContent' + align + '">';
8143 pp += '<div class="nGY2PopupCloseButton" style="font-size:0.9em;">' + G.O.icons.buttonClose + '</div>';
8144 pp += '<div class="nGY2PopupTitle">' + title + '</div>';
8145 pp += content;
8146 pp += '</div></div>';
8147
8148 G.popup.$elt = jQuery(pp).appendTo('body');
8149 setElementOnTop( G.VOM.$viewer, G.popup.$elt);
8150
8151 G.popup.isDisplayed = true;
8152
8153 var tweenable = new NGTweenable();
8154 tweenable.tween({
8155 from: { o: 0, y: 100 },
8156 to: { o: 1, y: 0 },
8157 easing: 'easeInOutSine',
8158 duration: 250,
8159 step: function (state, att) {
8160 G.popup.$elt[0].style.opacity = state.o;
8161 G.popup.$elt[0].style[G.CSStransformName] = 'translateY(' + (state.y) + 'px)';
8162 }
8163 });
8164
8165 G.popup.$elt.find('.nGY2PopupCloseButton').on('click', function(e) {
8166 e.stopPropagation();
8167 G.popup.close();
8168 });
8169
8170 }
8171
8172
8173 function GalleryMouseEnter(e) {
8174 if( !G.VOM.viewerDisplayed && G.GOM.albumIdx != -1 ) {
8175 var r = GalleryEventRetrieveElementl(e, true);
8176 // if( r.action == 'OPEN' && r.GOMidx != -1 ) {
8177 if( r.GOMidx != -1 ) {
8178 // var target = e.target || e.srcElement;
8179 // if( target.getAttribute('class') != 'nGY2GThumbnail' ) { return; }
8180 ThumbnailHover(r.GOMidx);
8181 }
8182 }
8183 }
8184
8185 function GalleryMouseLeave(e) {
8186 if( !G.VOM.viewerDisplayed && G.GOM.albumIdx != -1 ) {
8187 var r = GalleryEventRetrieveElementl(e, true);
8188 if( r.GOMidx != -1 ) {
8189 // var target = e.target || e.srcElement;
8190 // if( target.getAttribute('class') != 'nGY2GThumbnail' ) { return; }
8191 ThumbnailHoverOut(r.GOMidx);
8192 }
8193 }
8194 }
8195
8196 function GalleryEventRetrieveElementl( e, ignoreSubItems ) {
8197 var r = { action: 'NONE', GOMidx: -1 };
8198
8199 if( e == undefined ) {
8200 return r;
8201 }
8202 var target = e.target || e.srcElement;
8203 while( target != G.$E.conTnParent[0] ) { // loop element parent up to find the thumbnail element
8204 if( jQuery(target).hasClass('nGY2GThumbnail') ) {
8205 if( r.action == 'NONE' ) {
8206 r.action = 'OPEN';
8207 }
8208 r.GOMidx = jQuery(target).data('index');
8209 return r;
8210 }
8211 // if( !ignoreSubItems && jQuery(target).hasClass('nGY2GThumbnailIcon') ) {
8212 if( !ignoreSubItems ) {
8213 var a = jQuery(target).data('ngy2action');
8214 if( a != '' && a != undefined ) {
8215 r.action = a;
8216 }
8217 }
8218 if( target.parentNode == null ) {
8219 return r;
8220 }
8221 target = target.parentNode;
8222 }
8223 return r;
8224 }
8225
8226
8227 // OPEN ONE THUMBNAIL
8228 function ThumbnailOpen( idx, ignoreSelected ) {
8229 var item = G.I[idx];
8230
8231 G.GOM.albumIdxLoading = idx; // store idx -> may be used to display loader on album thumbnail
8232
8233 var fu = G.O.fnThumbnailClicked;
8234 if( fu !== null ) {
8235 typeof fu == 'function' ? fu(item.$elt, item) : window[fu](item.$elt, item);
8236 }
8237
8238 // open URL
8239 if( item.destinationURL !== undefined && item.destinationURL.length > 0 ) {
8240 window.location = item.destinationURL;
8241 return;
8242 }
8243
8244 switch( item.kind ) {
8245 case 'image':
8246 if( ignoreSelected === false && G.GOM.nbSelected > 0 ) {
8247 ThumbnailSelectionToggle(idx);
8248 }
8249 else {
8250 // display image
8251 DisplayPhotoIdx( idx );
8252 }
8253 break;
8254 case 'album':
8255 if( ignoreSelected === false && G.GOM.nbSelected > 0 ) {
8256 ThumbnailSelectionToggle( idx );
8257 }
8258 else {
8259 if( G.O.thumbnailAlbumDisplayImage && idx != 0 ) {
8260 // display album content in lightbox
8261 DisplayFirstMediaInAlbum( idx );
8262 return;
8263 }
8264 else {
8265 // display album content in gallery
8266 DisplayAlbum('-1', item.GetID());
8267 }
8268 }
8269 break;
8270 case 'albumUp':
8271 var parent = NGY2Item.Get(G, item.albumID);
8272 DisplayAlbum('-1', parent.albumID);
8273 break;
8274 }
8275 }
8276
8277 function DisplayFirstMediaInAlbum( albumIdx ) {
8278 if( G.O.debugMode ) { console.log('#DisplayFirstPhotoInAlbum : '+ albumIdx); }
8279
8280 var item = G.I[albumIdx];
8281
8282 var l = G.I.length;
8283 for( var i = 0; i < l; i++ ) {
8284 if( G.I[i].albumID == item.GetID() ) {
8285 DisplayPhotoIdx( i );
8286 return;
8287 }
8288 }
8289
8290 // load album content
8291 AlbumGetContent( item.GetID(), DisplayFirstMediaInAlbum, albumIdx, null );
8292
8293 }
8294
8295
8296 // Open link to original image (new window)
8297 function OpenOriginal( item ) {
8298 switch( G.O.kind ) {
8299 case 'flickr':
8300 var sU = 'https://www.flickr.com/photos/' + G.O.userID + '/' + item.GetID();
8301 if( item.albumID != '0' ) {
8302 sU += '/in/album-' + item.albumID + '/';
8303 }
8304 window.open(sU, '_blank');
8305 break;
8306 case 'picasa':
8307 case 'google':
8308 case 'google2':
8309 // no more working since Google changed the access to Google Photos in 2017
8310 // var sU='https://plus.google.com/photos/'+G.O.userID+'/albums/'+item.albumID+'/'+item.GetID();
8311 // window.open(sU,'_blank');
8312 // break;
8313 default:
8314 var sU = item.responsiveURL();
8315 window.open(sU, '_blank');
8316 break;
8317 }
8318 }
8319
8320 // ########################################################
8321 // DISPLAY ONE MEDIA
8322 // with internal or external viewer
8323 // ########################################################
8324 function DisplayPhotoIdx( ngy2ItemIdx ) {
8325
8326 if( !G.O.thumbnailOpenInLightox ) { return; }
8327
8328 if( G.O.thumbnailOpenOriginal ) {
8329 // Open link to original image
8330 OpenOriginal( G.I[ngy2ItemIdx] );
8331 return;
8332 }
8333
8334 var items = [];
8335// G.VOM.currItemIdx = 0;
8336 G.VOM.content.current.vIdx = 0;
8337 G.VOM.items = [];
8338 G.VOM.albumID = G.I[ngy2ItemIdx].albumID;
8339
8340 var vimg = new VImg(ngy2ItemIdx);
8341 G.VOM.items.push(vimg);
8342 items.push(G.I[ngy2ItemIdx]);
8343 //TODO -> danger? -> pourquoi reconstruire la liste si d�j� ouvert (back/forward)
8344 var l = G.I.length;
8345 for( let idx = ngy2ItemIdx+1; idx < l ; idx++) {
8346 let item = G.I[idx];
8347 if( item.kind == 'image' && item.isToDisplay(G.VOM.albumID) && item.destinationURL == '' ) {
8348 let vimg = new VImg(idx);
8349 G.VOM.items.push(vimg);
8350 items.push(item);
8351 }
8352 }
8353 var last = G.VOM.items.length;
8354 var cnt = 1;
8355 for( let idx = 0; idx < ngy2ItemIdx ; idx++) {
8356 let item = G.I[idx];
8357 if( item.kind == 'image' && item.isToDisplay(G.VOM.albumID) && item.destinationURL == '' ) {
8358 let vimg = new VImg(idx);
8359 vimg.mediaNumber = cnt;
8360 G.VOM.items.push(vimg);
8361 items.push(item);
8362 cnt++;
8363 }
8364 }
8365 for( let i = 0; i < last; i++ ) {
8366 G.VOM.items[i].mediaNumber = cnt;
8367 cnt++;
8368 }
8369
8370 // opens media with external viewer
8371 var fu = G.O.fnThumbnailOpen;
8372 if( fu !== null ) {
8373 typeof fu == 'function' ? fu(items) : window[fu](items);
8374 return;
8375 }
8376
8377 // use internal viewer
8378 if( !G.VOM.viewerDisplayed ) {
8379 // build viewer and display
8380 LightboxOpen();
8381 }
8382 else {
8383 // viewer already displayed -> display new media in current viewer
8384 G.VOM.content.current.$media.empty();
8385 let item = G.VOM.content.current.NGY2Item();
8386 var spreloader = '<div class="nGY2ViewerMediaLoaderDisplayed"></div>';
8387 if( item.mediaKind == 'img' && item.imageWidth != 0 && item.imageHeight != 0 ) {
8388 spreloader = '<div class="nGY2ViewerMediaLoaderHidden"></div>';
8389 }
8390 G.VOM.content.current.$media.append( spreloader + item.mediaMarkup);
8391 ViewerSetMediaVisibility(G.VOM.content.next, 0);
8392 ViewerSetMediaVisibility(G.VOM.content.previous, 0);
8393 if( item.mediaKind == 'img' ) {
8394 G.VOM.ImageLoader.loadImage(VieweImgSizeRetrieved, item);
8395 }
8396 // G.VOM.$mediaCurrent.css({ opacity:0 }).attr('src','');
8397 // G.VOM.ImageLoader.loadImage(VieweImgSizeRetrieved, G.VOM.NGY2Item(0));
8398 // G.VOM.$mediaCurrent.children().eq(0).attr('src',G.emptyGif).attr('src', G.VOM.NGY2Item(0).responsiveURL());
8399 // LightboxDisplay(0, '');
8400 LightboxDisplay('');
8401 }
8402 }
8403
8404 function ViewerZoomStart() {
8405 if( G.O.viewerZoom && !G.VOM.viewerMediaIsChanged ) {
8406 var item = G.VOM.content.current.NGY2Item();
8407 if( item.imageHeight > 0 && item.imageWidth > 0 ) {
8408 if( G.VOM.zoom.isZooming === false ) {
8409 // default zoom
8410 G.VOM.zoom.userFactor = 1;
8411 G.VOM.zoom.isZooming = true;
8412 }
8413 return true;
8414 }
8415 }
8416 }
8417
8418 function ViewerZoomIn( zoomIn ) {
8419 if( zoomIn ) {
8420 // zoom in
8421 G.VOM.zoom.userFactor += 0.1;
8422 ViewerZoomMax();
8423 }
8424 else {
8425 // zoom out
8426 G.VOM.zoom.userFactor -= 0.1;
8427 ViewerZoomMin();
8428 }
8429 ViewerMediaSetPosAndZoom();
8430 }
8431
8432 function ViewerZoomMax() {
8433 if( G.VOM.zoom.userFactor > 3 ) {
8434 G.VOM.zoom.userFactor = 3;
8435 }
8436 }
8437 function ViewerZoomMin() {
8438
8439 if( G.VOM.zoom.userFactor < 0.2 ) {
8440 G.VOM.zoom.userFactor = 0.2;
8441 }
8442 }
8443
8444
8445
8446 // Set position and size of all 3 media containers
8447 function ViewerMediaSetPosAndZoom() {
8448
8449 if( !G.VOM.zoom.isZooming ) {
8450 G.VOM.zoom.userFactor = 1;
8451 }
8452 // window.ng_draf( function() {
8453 ViewerMediaSetPosAndZoomOne( G.VOM.content.current, true );
8454 ViewerMediaSetPosAndZoomOne( G.VOM.content.previous, false );
8455 ViewerMediaSetPosAndZoomOne( G.VOM.content.next, false );
8456 // });
8457 }
8458
8459
8460
8461 // Media which is not IMG -> center and set size
8462 function ViewerMediaCenterNotImg( $mediaContainer ) {
8463 var $media = $mediaContainer.children().eq(1);
8464 var h = 90;
8465 if( G.O.viewerGallery != 'none' ) { h -= 10; }
8466 if( G.O.viewerToolbar.display != 'none' ) { h -= 10; }
8467 $media.css( {'height': h+'%' });
8468 $media.css( {'width': '90%' });
8469 $media[0].style[G.CSStransformName] = 'translate(0px, "50%") ';
8470 }
8471
8472 // Set position and size of ONE media container
8473 function ViewerMediaSetPosAndZoomOne(content_item, isCurrent ) {
8474
8475 var item = content_item.NGY2Item();
8476 var $img = content_item.$media;
8477
8478
8479 if( item.mediaKind != 'img' ) {
8480 ViewerMediaCenterNotImg( $img );
8481 return;
8482 }
8483
8484 if( item.imageHeight == 0 || item.imageWidth == 0 ) {
8485 // ViewerSetMediaVisibility( item, $img, 0 );
8486 ViewerSetMediaVisibility( content_item, 0 );
8487 return;
8488 }
8489
8490 // part 1: set the image size
8491 var zoomUserFactor = isCurrent == true ? G.VOM.zoom.userFactor : 1;
8492
8493 var dpr = 1;
8494 if( G.O.viewerImageDisplay == 'bestImageQuality' ) {
8495 dpr = window.devicePixelRatio;
8496 }
8497
8498 // retrieve the base zoom factor (image fill screen)
8499 var zoomBaseFactorW = (G.VOM.window.lastWidth - G.VOM.padding.V) / (item.imageWidth / dpr);
8500 var zoomBaseFactorH = (G.VOM.window.lastHeight - G.VOM.padding.H) / (item.imageHeight / dpr);
8501 var zoomBaseFactor = Math.min(zoomBaseFactorW, zoomBaseFactorH);
8502 if( zoomBaseFactor > 1 && G.O.viewerImageDisplay != 'upscale' ) {
8503 // no upscale
8504 zoomBaseFactor = 1;
8505 }
8506
8507 var imageCurrentHeight = (item.imageHeight / dpr) * zoomUserFactor * zoomBaseFactor;
8508 var imageCurrentWidth = (item.imageWidth / dpr) * zoomUserFactor * zoomBaseFactor;
8509 $img.children().eq(1).css( {'height': imageCurrentHeight });
8510 $img.children().eq(1).css( {'width': imageCurrentWidth });
8511
8512 // retrieve posX/Y to center image
8513 var posX = 0;
8514 if( imageCurrentWidth > G.VOM.window.lastWidth ) {
8515 posX = -(imageCurrentWidth - G.VOM.window.lastWidth)/2;
8516 }
8517 var posY = 0;
8518 // if( imageCurrentHeight > G.VOM.window.lastHeight ) {
8519 // posY = ( imageCurrentHeight - G.VOM.window.lastHeight ) / 2;
8520 // }
8521 // posY = 0; // actually, it seems that the image is always centered vertically -> so no need to to anything
8522
8523 // Part 2: set the X/Y position (for zoom/pan)
8524 if( isCurrent ) {
8525 if( !G.VOM.zoom.isZooming ) {
8526 G.VOM.panPosX = 0;
8527 G.VOM.panPosY = 0;
8528 }
8529 G.VOM.zoom.posX = posX;
8530 G.VOM.zoom.posY = posY;
8531 ViewerImagePanSetPosition(G.VOM.panPosX, G.VOM.panPosY, $img, false);
8532 }
8533 // else {
8534 //$img[0].style[G.CSStransformName]= 'translate3D('+ posX+'px, '+ posY+'px, 0) ';
8535 // }
8536 else {
8537 // set the pan position of each media container
8538 ViewerMediaPanX( G.VOM.swipePosX );
8539 $img.children().eq(1)[0].style[G.CSStransformName]= 'translate(0px, 0px) rotate('+ item.rotationAngle +'deg)';
8540 }
8541
8542 }
8543
8544 // position the image depending on the zoom factor and the pan X/Y position
8545 // IMG is the only media supporting zoom/pan
8546 function ViewerImagePanSetPosition(posX, posY, imageContainer, savePosition ) {
8547 if( savePosition ) {
8548 G.VOM.panPosX = posX;
8549 G.VOM.panPosY = posY;
8550 }
8551
8552 posX += G.VOM.zoom.posX;
8553 posY += G.VOM.zoom.posY;
8554
8555 // imageContainer.children().eq(1)[0].style[G.CSStransformName]= 'translate('+ posX + 'px, '+ posY + 'px)';
8556 imageContainer.children().eq(1)[0].style[G.CSStransformName]= 'translate('+ posX + 'px, '+ posY + 'px) rotate('+ G.VOM.content.current.NGY2Item().rotationAngle +'deg)';
8557
8558
8559 }
8560
8561
8562 // LIGHTBOX
8563 // display media with internal viewer
8564 function LightboxOpen( idx ) {
8565
8566 // G.VOM.viewerDisplayed = true;
8567 G.GOM.firstDisplay = false;
8568
8569 // remove scrollbar and add right margin with same width as the scrollbar to avoid page reflow
8570 jQuery('head').append('<style id="nGY2_body_scrollbar_style" type="text/css">.nGY2_body_scrollbar{margin-right: ' + (window.innerWidth - document.documentElement.clientWidth) + 'px;}</style>');
8571 jQuery("body").addClass("nGY2_body_scrollbar");
8572
8573
8574 G.VOM.$baseCont = jQuery('<div class="nGY2 nGY2ViewerContainer" style="opacity:1"></div>').appendTo('body');
8575
8576 SetViewerTheme();
8577
8578 G.VOM.$viewer = jQuery('<div class="nGY2Viewer" style="opacity:0" itemscope itemtype="http://schema.org/ImageObject"></div>').appendTo( G.VOM.$baseCont );
8579 G.VOM.$viewer.css({ msTouchAction: 'none', touchAction: 'none' }); // avoid pinch zoom
8580
8581 if( idx == undefined ) {
8582 G.VOM.content.current.vIdx = 0;
8583 }
8584 else {
8585 G.VOM.content.current.vIdx = idx;
8586 }
8587 G.VOM.content.previous.vIdx = G.VOM.IdxNext();
8588 G.VOM.content.next.vIdx = G.VOM.IdxPrevious();
8589
8590 var sMedia = '<div class="nGY2ViewerMediaPan"><div class="nGY2ViewerMediaLoaderDisplayed"></div>' + G.VOM.content.previous.NGY2Item().mediaMarkup + '</div>'; // previous media
8591 sMedia += '<div class="nGY2ViewerMediaPan"><div class="nGY2ViewerMediaLoaderDisplayed"></div>' + G.VOM.content.current.NGY2Item().mediaMarkup + '</div>'; // current media
8592 sMedia += '<div class="nGY2ViewerMediaPan"><div class="nGY2ViewerMediaLoaderDisplayed"></div>' + G.VOM.content.next.NGY2Item().mediaMarkup + '</div>'; // next media
8593
8594 var sNav = '';
8595 var iconP = G.O.icons.viewerImgPrevious;
8596 if( iconP != undefined && iconP != '') {
8597 sNav += '<div class="nGY2ViewerAreaPrevious ngy2viewerToolAction" data-ngy2action="previous">' + iconP + '</div>';
8598 }
8599 var iconN = G.O.icons.viewerImgNext;
8600 if( iconN != undefined && iconN != '') {
8601 sNav += '<div class="nGY2ViewerAreaNext ngy2viewerToolAction" data-ngy2action="next">' + iconN + '</div>';
8602 }
8603
8604 G.VOM.$content = jQuery('<div class="nGY2ViewerContent">' + sMedia + sNav + '</div>').appendTo( G.VOM.$viewer );
8605
8606 G.VOM.$buttonLeft = G.VOM.$content.find('.nGY2ViewerAreaPrevious');
8607 G.VOM.$buttonRight = G.VOM.$content.find('.nGY2ViewerAreaNext');
8608
8609 var $mediaPan = G.VOM.$content.find('.nGY2ViewerMediaPan');
8610 G.VOM.content.previous.$media = $mediaPan.eq(0); // pointer to previous media container
8611 G.VOM.content.current.$media = $mediaPan.eq(1); // pointer to current media container
8612 G.VOM.content.next.$media = $mediaPan.eq(2); // pointer to next media container
8613
8614 G.VOM.ImageLoader.loadImage( VieweImgSizeRetrieved, G.VOM.content.current.NGY2Item() );
8615 G.VOM.ImageLoader.loadImage( VieweImgSizeRetrieved, G.VOM.content.previous.NGY2Item() );
8616 G.VOM.ImageLoader.loadImage( VieweImgSizeRetrieved, G.VOM.content.next.NGY2Item() );
8617
8618 G.VOM.padding.H = parseInt(G.VOM.$content.css("padding-left")) + parseInt(G.VOM.$content.css("padding-right"));
8619 G.VOM.padding.V = parseInt(G.VOM.$content.css("padding-top")) + parseInt(G.VOM.$content.css("padding-bottom"));
8620
8621 // build media toolbar container
8622 var vtbBg1 = '';
8623 var vtbBg2 = ' toolbarBackground';
8624 if( G.O.viewerToolbar.fullWidth ) {
8625 vtbBg1 = ' toolbarBackground';
8626 vtbBg2 = '';
8627 }
8628 var vtbAlign = 'text-align:center;';
8629 switch ( G.O.viewerToolbar.align ) {
8630 case 'left':
8631 vtbAlign = 'text-align:left;';
8632 break;
8633 case 'right':
8634 vtbAlign = 'text-align:right;';
8635 break;
8636 }
8637 var sTB = '<div class="toolbarContainer nGEvent' + vtbBg1 + '" style="visibility:' +(G.O.viewerToolbar.display ? "visible" : "hidden")+';'+vtbAlign+'"><div class="toolbar nGEvent' + vtbBg2 + '"></div></div>';
8638 G.VOM.$toolbar = jQuery(sTB).appendTo(G.VOM.$viewer);
8639
8640 if( G.VOM.toolbarMode == 'min' || (G.O.viewerToolbar.autoMinimize > 0 && G.O.viewerToolbar.autoMinimize >= G.GOM.cache.viewport.w) ) {
8641 ViewerToolbarForVisibilityMin();
8642 }
8643 else {
8644 ViewerToolbarForVisibilityStd();
8645 }
8646
8647 // top-left toolbar
8648 var sTopLeft = '<div class="nGY2ViewerToolsTopLeft nGEvent"><div class="toolbar nGEvent">';
8649 var sTL = G.O.viewerTools.topLeft.split(',');
8650 for( var i = 0, sTLL = sTL.length; i < sTLL; i++) {
8651 sTopLeft += ToolbarAddElt( sTL[i] );
8652 }
8653 sTopLeft += '</div></div>';
8654 G.VOM.$toolbarTL = jQuery(sTopLeft).appendTo(G.VOM.$viewer);
8655
8656 // top-right toolbar
8657 var sTopRight = '<div class="nGY2ViewerToolsTopRight nGEvent"><div class="toolbar nGEvent">';
8658 var sTR = G.O.viewerTools.topRight.split(',');
8659 for( var i = 0, sTRL = sTR.length; i < sTRL; i++) {
8660 sTopRight += ToolbarAddElt( sTR[i] );
8661 }
8662 sTopRight += '</div></div>';
8663 G.VOM.$toolbarTR = jQuery(sTopRight).appendTo(G.VOM.$viewer);
8664
8665 // set the events handler on the toolbars
8666 ViewerToolsOn();
8667
8668 // Go to fullscreen mode
8669 if( ngscreenfull.enabled && G.O.viewerFullscreen ) {
8670 ngscreenfull.request();
8671 G.VOM.viewerIsFullscreen=true;
8672 }
8673
8674 // Gallery
8675 LightboxGalleryBuild();
8676
8677 setElementOnTop('', G.VOM.$viewer);
8678 ResizeLightbox(true);
8679 G.VOM.gallery.Resize();
8680 G.VOM.timeImgChanged = new Date().getTime();
8681
8682 // viewer display transition
8683 G.VOM.$toolbarTL.css('opacity', 0);
8684 G.VOM.$toolbarTR.css('opacity', 0);
8685 G.VOM.$buttonLeft.css('opacity', 0);
8686 G.VOM.$buttonRight.css('opacity', 0);
8687 if( G.O.viewerGallery != 'none' ) { G.VOM.gallery.$elt.css('opacity', 0); }
8688 G.VOM.$content.css('opacity', 0);
8689 G.VOM.$toolbarTR[0].style[G.CSStransformName] = 'translateY(-40px) ';
8690 G.VOM.$toolbarTL[0].style[G.CSStransformName] = 'translateY(-40px) ';
8691 G.VOM.$buttonLeft[0].style[G.CSStransformName] = 'translateX(-40px) ';
8692 G.VOM.$buttonRight[0].style[G.CSStransformName] = 'translateX(40px) ';
8693
8694 // STEP 1: display main container, including media
8695 new NGTweenable().tween({
8696 from: { opacity: 0, posY: G.VOM.window.lastHeight*.5 },
8697 to: { opacity: 1, posY: 0 },
8698 delay: 10,
8699 duration: 450,
8700 easing: 'easeInOutQuint',
8701 step: function (state) {
8702 // lightbox
8703 G.VOM.$viewer.css('opacity', state.opacity);
8704 G.VOM.$viewer[0].style[G.CSStransformName] = 'translateY(' + (state.posY) + 'px) ';
8705
8706 // media in lightbox
8707 G.VOM.$content.css('opacity', state.opacity);
8708 }
8709 });
8710
8711
8712 // STEP 2: display tools, left/right navigation buttons, gallery
8713 new NGTweenable().tween({
8714 from: { posY: -40, opacity: 0, scale: 3 },
8715 to: { posY: 0, opacity: 1, scale: 1 },
8716 delay: 300,
8717 duration: 400,
8718 easing: 'easeInOutQuint',
8719 step: function (state) {
8720
8721 // tools
8722 G.VOM.$toolbarTR[0].style[G.CSStransformName] = 'translateY(' + (state.posY) + 'px) ';
8723 G.VOM.$toolbarTL[0].style[G.CSStransformName] = 'translateY(' + (state.posY) + 'px) ';
8724 G.VOM.$buttonLeft[0].style[G.CSStransformName] = 'translateX(' + (state.posY) + 'px) ';
8725 G.VOM.$buttonRight[0].style[G.CSStransformName] = 'translateX(' + (-state.posY) + 'px) ';
8726
8727 // gallery
8728 if( G.O.viewerGallery != 'none' ) {
8729 G.VOM.gallery.$elt.css({ opacity: state.opacity });
8730 G.VOM.gallery.$elt[0].style[G.CSStransformName] = 'scale('+state.scale+')';
8731 }
8732
8733 },
8734 finish: function() {
8735 G.VOM.viewerDisplayed = true;
8736 ViewerMediaPanX(0);
8737 ViewerSetEvents();
8738
8739 LightboxDisplay('');
8740
8741 if( G.O.slideshowAutoStart ) {
8742 G.VOM.playSlideshow = false;
8743 SlideshowToggle();
8744 }
8745
8746 ViewerToolsUnHide();
8747 LightboxDisplayFinalize('');
8748 }
8749 });
8750
8751
8752
8753
8754 // stop click propagation on media ==> if the user clicks outside of an media, the viewer is closed
8755 // --> no more supported since v2.0.0
8756 // G.VOM.$viewer.find('img').on('click', function (e) { e.stopPropagation(); });
8757
8758
8759 // ViewerMediaPanX(0);
8760 // ViewerSetEvents();
8761
8762 // LightboxDisplay('');
8763
8764 // if( G.O.slideshowAutoStart ) {
8765 // G.VOM.playSlideshow = false;
8766 // SlideshowToggle();
8767 // }
8768 }
8769
8770 function ViewerEvents() {
8771 if( !G.VOM.viewerDisplayed || G.VOM.viewerMediaIsChanged ) {
8772 // if( !G.VOM.viewerDisplayed || G.VOM.viewerMediaIsChanged || G.VOM.content.current.NGY2Item().mediaKind != 'img') {
8773 // ignore fired event if viewer not displayed or if currently changed (or if current media not an image)
8774 return false;
8775 }
8776 return true;
8777 }
8778
8779 // VIEWER - BUILD THE THUMBNAILS GALLERY
8780 function LightboxGalleryBuild() {
8781
8782 G.VOM.gallery.firstDisplay = true;
8783
8784 if( G.O.viewerGallery != 'none' ) {
8785
8786 var tw = G.O.viewerGalleryTWidth;
8787 var th = G.O.viewerGalleryTHeight;
8788 var gutter = 2;
8789
8790 var t = '';
8791 for( var i=0; i< G.VOM.items.length; i++) {
8792 var idx = G.VOM.items[i].ngy2ItemIdx;
8793 var o = G.I[idx];
8794 var src = (o.thumbImg().src).replace(/'/g, "%27"); // replace single quote with %27
8795 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>';
8796 }
8797 G.VOM.gallery.gwidth = (tw+2*gutter) * G.VOM.items.length;
8798 G.VOM.gallery.oneTmbWidth = tw+2*gutter;
8799 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>";
8800 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);
8801 G.VOM.gallery.$tmbCont = G.VOM.gallery.$elt.find('.nGY2VThumbnailContainer')
8802
8803 G.VOM.gallery.Resize();
8804 G.VOM.gallery.SetThumbnailActive();
8805
8806 }
8807 }
8808
8809
8810 // Lightbox gesture handling
8811 function ViewerSetEvents() {
8812
8813 if( G.VOM.hammertime == null ) {
8814
8815 G.VOM.hammertime = new NGHammer.Manager(G.VOM.$baseCont[0], {
8816 // domEvents: true,
8817 recognizers: [
8818 [NGHammer.Pinch, { enable: true }],
8819 [NGHammer.Pan, { direction: NGHammer.DIRECTION_ALL }]
8820 ]
8821 });
8822
8823 // PAN
8824 G.VOM.hammertime.on('pan', function(ev) {
8825 if( !ViewerEvents() ) { return; }
8826
8827
8828 if( G.VOM.panMode == 'off' ) {
8829 // PAN START -> determine the element to pan
8830 if( ev.target.dataset.ngy2_lightbox_thumbnail != undefined || ev.target.dataset.ngy2_lightbox_gallery != undefined ){
8831 G.VOM.panMode = 'gallery';
8832 }
8833 else {
8834 if( G.VOM.zoom.isZooming ) {
8835 G.VOM.panMode = 'zoom';
8836 }
8837 else {
8838 G.VOM.panMode = 'media';
8839 }
8840 }
8841 }
8842
8843 // PAN the determined element
8844 switch( G.VOM.panMode ) {
8845 case 'zoom':
8846 // pan zoomed image
8847 ViewerImagePanSetPosition(G.VOM.panPosX + ev.deltaX, G.VOM.panPosY + ev.deltaY, G.VOM.content.current.$media, false);
8848 G.VOM.toolsHide();
8849 break;
8850
8851 case 'media':
8852 if( Math.abs(ev.deltaY) > G.VOM.panThreshold && Math.abs(ev.deltaX) < G.VOM.panThreshold && !G.VOM.panXOnly ) {
8853 // pan viewer down/up to close the lightbox
8854 ViewerMediaPanX( 0 );
8855 var dist = 0;
8856 if( ev.deltaY < 0 ) {
8857 // pan up
8858 dist = Math.max( ev.deltaY, -200);
8859 }
8860 else {
8861 // pan down
8862 dist = Math.min( ev.deltaY, 200);
8863 }
8864 G.VOM.$viewer[0].style[G.CSStransformName] = 'translateY(' + dist + 'px) ';
8865 G.VOM.$viewer.css('opacity', 1-Math.abs(dist)/200/2);
8866 }
8867 else {
8868 // pan media left/right
8869 if( Math.abs(ev.deltaX) > G.VOM.panThreshold ) {
8870 G.VOM.panXOnly = true;
8871 }
8872 ViewerMediaPanX( ev.deltaX );
8873 G.VOM.$viewer[0].style[G.CSStransformName] = 'translateY(0px)';
8874 G.VOM.$viewer.css('opacity', 1);
8875 }
8876 break;
8877
8878 case 'gallery':
8879 G.VOM.gallery.PanGallery( ev.deltaX );
8880 break;
8881 }
8882
8883 });
8884
8885 // PAN END
8886 G.VOM.hammertime.on('panend', function(ev) {
8887 if( !ViewerEvents() ) { return; }
8888
8889 switch( G.VOM.panMode ) {
8890 case 'zoom':
8891 // PAN END in image zoom mode
8892 G.VOM.timeImgChanged = new Date().getTime();
8893 ViewerImagePanSetPosition( G.VOM.panPosX+ev.deltaX, G.VOM.panPosY+ev.deltaY, G.VOM.content.current.$media, true);
8894 break;
8895 case 'media':
8896 var panY = false;
8897 if( !G.VOM.panXOnly ) {
8898 if( Math.abs(ev.deltaY) > 50 && Math.abs(ev.deltaX) < 50 ) {
8899 // close viewer
8900 LightboxClose();
8901 panY = true;
8902 }
8903 }
8904 if( !panY ) {
8905 if( Math.abs( ev.deltaX ) < 50 ) {
8906 ViewerMediaPanX(0);
8907 }
8908 else {
8909 ev.deltaX > 50 ? DisplayPreviousMedia( Math.abs(ev.velocityX) ) : DisplayNextMedia( Math.abs(ev.velocityX) );
8910 }
8911 }
8912 G.VOM.panXOnly = false;
8913 break;
8914 case 'gallery':
8915 // PAN END on thumbnail gallery
8916 G.VOM.gallery.posX += ev.deltaX;
8917 G.VOM.gallery.PanGallery( 0 );
8918 G.VOM.gallery.PanGalleryEnd( ev.velocityX );
8919 break;
8920 }
8921
8922 G.VOM.panMode = 'off';
8923 });
8924
8925
8926 // ZOOM FEATURE ENABLED
8927 if( G.O.viewerZoom ) {
8928
8929 G.VOM.hammertime.add( new NGHammer.Tap({ event: 'doubletap', taps: 2, interval: 250 }) );
8930 G.VOM.hammertime.add( new NGHammer.Tap({ event: 'singletap' }) );
8931 G.VOM.hammertime.get('doubletap').recognizeWith('singletap');
8932 G.VOM.hammertime.get('singletap').requireFailure('doubletap');
8933
8934 // single tap -> next/previous media
8935 G.VOM.hammertime.on('singletap', function(ev) {
8936
8937 if( !ViewerEvents() ) { return; }
8938
8939 // Gallery on viewer -> click/touch on one thumbnail -> display corresponding image
8940 if( ev.target.dataset.ngy2_lightbox_thumbnail != undefined ){
8941
8942 var idx = parseInt(ev.target.dataset.ngy2_idx);
8943 var vidx = parseInt(ev.target.dataset.ngy2_vidx);
8944
8945 if( !isNaN(idx) && vidx != G.VOM.content.current.vIdx ) {
8946
8947 if( vidx > G.VOM.content.current.vIdx ) {
8948 TriggerCustomEvent('lightboxNextImage');
8949
8950 // replace the next media with selected media
8951 G.VOM.content.next.$media.empty();
8952 var nextItem = G.I[idx];
8953 G.VOM.content.next.vIdx = vidx;
8954 let spreloader = '<div class="nGY2ViewerMediaLoaderDisplayed"></div>';
8955 if( nextItem.mediaKind == 'img' && nextItem.imageWidth != 0 && nextItem.imageHeight != 0 ) {
8956 spreloader = '<div class="nGY2ViewerMediaLoaderHidden"></div>';
8957 }
8958 G.VOM.content.next.$media.append( spreloader + nextItem.mediaMarkup );
8959 if( nextItem.mediaKind == 'img' ) {
8960 G.VOM.ImageLoader.loadImage(VieweImgSizeRetrieved, nextItem);
8961 }
8962 else {
8963 ViewerMediaCenterNotImg( G.VOM.content.next.$media );
8964 }
8965 LightboxDisplay('nextImage');
8966
8967 }
8968 else {
8969 TriggerCustomEvent('lightboxPreviousImage');
8970
8971 // replace the previous media with selected media
8972 G.VOM.content.previous.$media.empty();
8973 var previousItem = G.I[idx];
8974 G.VOM.content.previous.vIdx = vidx;
8975 let spreloader = '<div class="nGY2ViewerMediaLoaderDisplayed"></div>';
8976 if( previousItem.mediaKind == 'img' && previousItem.imageWidth != 0 && previousItem.imageHeight != 0 ) {
8977 spreloader = '<div class="nGY2ViewerMediaLoaderHidden"></div>';
8978 }
8979 G.VOM.content.previous.$media.append( spreloader + previousItem.mediaMarkup );
8980 if( previousItem.mediaKind == 'img' ) {
8981 G.VOM.ImageLoader.loadImage(VieweImgSizeRetrieved, previousItem);
8982 }
8983 else {
8984 ViewerMediaCenterNotImg( G.VOM.content.previous.$media );
8985 }
8986 LightboxDisplay('previousImage');
8987 }
8988 return;
8989 }
8990 }
8991
8992
8993 StopPropagationPreventDefault(ev.srcEvent);
8994 if( G.VOM.toolbarsDisplayed == false ) {
8995 debounce( ViewerToolsUnHide, 100, false)();
8996 G.VOM.singletapTime = new Date().getTime();
8997 }
8998 else {
8999 // toolbars are displayed -> display next/previous media
9000 if( (new Date().getTime()) - G.VOM.singletapTime < 400 ) { return; } // to avoid conflict with MOUSEMOVE event
9001 if( ev.target.className.indexOf('nGY2ViewerMedia') !== -1 ) {
9002 var x =0;
9003 if( ev.srcEvent instanceof MouseEvent ) {
9004 x = ev.srcEvent.pageX;
9005 }
9006 else {
9007 x = ev.srcEvent.changedTouches[0].pageX;
9008 }
9009 if( x < (G.GOM.cache.viewport.w/2) ) {
9010 DisplayPreviousMedia();
9011 }
9012 else {
9013 DisplayNextMedia();
9014 }
9015 }
9016 }
9017 });
9018
9019 // double tap -> zoom
9020 G.VOM.hammertime.on('doubletap', function(ev) {
9021 if( !ViewerEvents() ) { return; }
9022 StopPropagationPreventDefault(ev.srcEvent);
9023
9024 if( ev.target.className.indexOf('nGY2ViewerMedia') !== -1 ) {
9025 // double tap only on image
9026 if( G.VOM.zoom.isZooming ) {
9027 G.VOM.zoom.isZooming = false;
9028 // G.VOM.zoom.userFactor = 1;
9029 ResizeLightbox(true);
9030 }
9031 else {
9032 if( ViewerZoomStart() ) {
9033 G.VOM.zoom.userFactor = 1.5;
9034 ViewerMediaSetPosAndZoom();
9035 }
9036 }
9037 }
9038 });
9039
9040 // pinch end
9041 G.VOM.hammertime.on('pinchend', function(ev) {
9042 ev.srcEvent.stopPropagation();
9043 ev.srcEvent.preventDefault(); // cancel mouseenter event
9044 G.VOM.timeImgChanged = new Date().getTime();
9045 });
9046 G.VOM.hammertime.on('pinch', function(ev) {
9047 ev.srcEvent.stopPropagation();
9048 ev.srcEvent.preventDefault(); // cancel mouseenter event
9049
9050 if( ViewerZoomStart() ) {
9051 G.VOM.zoom.userFactor = ev.scale;
9052 ViewerZoomMax();
9053 ViewerZoomMin();
9054 ViewerMediaSetPosAndZoom(); // center media
9055 }
9056 });
9057 }
9058
9059
9060 else {
9061 // ZOOM FEATURE DISABLED
9062
9063 G.VOM.hammertime.add( new NGHammer.Tap({ event: 'singletap' }) );
9064
9065 // click/tap on image to go to next/previous one
9066 // G.VOM.hammertime.on('tap', function(ev) {
9067 G.VOM.hammertime.on('singletap', function(ev) {
9068 if( !ViewerEvents() ) { return; }
9069 StopPropagationPreventDefault( ev.srcEvent );
9070 if( G.VOM.toolbarsDisplayed == false ){
9071 // display tools on tap if hidden
9072 debounce( ViewerToolsUnHide, 100, false)();
9073 G.VOM.singletapTime = new Date().getTime();
9074 }
9075 else {
9076 // toolbars are displayed -> display next/previous media
9077 if( (new Date().getTime()) - G.VOM.singletapTime < 400 ) { return; } // to avoid conflict with MOUSEMOVE event
9078 if( ev.target.className.indexOf('nGY2ViewerMedia') !== -1 ) {
9079 var x = 0;
9080 if( ev.srcEvent instanceof MouseEvent ) {
9081 x = ev.srcEvent.pageX;
9082 }
9083 else {
9084 x = ev.srcEvent.changedTouches[0].pageX;
9085 }
9086 if( x < (G.GOM.cache.viewport.w/2) ) {
9087 DisplayPreviousMedia();
9088 }
9089 else {
9090 DisplayNextMedia();
9091 }
9092 }
9093 }
9094
9095 });
9096 }
9097 }
9098 }
9099
9100
9101 function StopPropagationPreventDefault(e) {
9102 e.stopPropagation();
9103 e.preventDefault();
9104 }
9105
9106 // Hide toolbars on user inactivity
9107 function ViewerToolsHide() {
9108 if( G.VOM.viewerDisplayed ) {
9109 G.VOM.toolbarsDisplayed = false;
9110 ViewerToolsOpacity(0);
9111 }
9112 }
9113
9114 function ViewerToolsUnHide() {
9115 if( G.VOM.viewerDisplayed ) {
9116 G.VOM.toolbarsDisplayed = true;
9117 ViewerToolsOpacity(1);
9118 G.VOM.toolsHide(); // re-init delay before hide tools+gallery
9119 }
9120 }
9121
9122 function ViewerToolsOpacity( op ) {
9123 if( G.VOM.$toolbar != null ) {
9124 G.VOM.$toolbar.css('opacity', op);
9125 }
9126 if( G.VOM.$toolbarTL != null ) {
9127 G.VOM.$toolbarTL.css('opacity', op);
9128 }
9129 if( G.VOM.$toolbarTR != null ) {
9130 G.VOM.$toolbarTR.css('opacity', op);
9131 }
9132
9133 // next/previous
9134 G.VOM.$content.find('.nGY2ViewerAreaNext').css('opacity', op);
9135 G.VOM.$content.find('.nGY2ViewerAreaPrevious').css('opacity', op);
9136
9137 // gallery
9138 // G.VOM.gallery.$elt.css('opacity', op);
9139 }
9140
9141
9142
9143 function ViewerToolsOn() {
9144 // removes all events
9145 G.VOM.$viewer.off('touchstart click', '.ngy2viewerToolAction', ViewerToolsAction);
9146
9147 // action button
9148 G.VOM.$viewer.on('touchstart click', '.ngy2viewerToolAction', ViewerToolsAction);
9149 }
9150
9151
9152 // Actions of the button/elements
9153 function ViewerToolsAction(e) {
9154 // delay to avoid twice handling on smartphone/tablet (both touchstart click events are fired)
9155 if( (new Date().getTime()) - G.timeLastTouchStart < 300 ) { return; }
9156 G.timeLastTouchStart = new Date().getTime();
9157
9158 var $this = $(this);
9159 var ngy2action = $this.data('ngy2action');
9160 if( ngy2action == undefined ) { return; }
9161 switch( ngy2action ) {
9162 case 'next':
9163 StopPropagationPreventDefault(e);
9164 DisplayNextMedia();
9165 break;
9166 case 'previous':
9167 StopPropagationPreventDefault(e);
9168 DisplayPreviousMedia();
9169 break;
9170 case 'playPause':
9171 e.stopPropagation();
9172 SlideshowToggle();
9173 break;
9174 case 'zoomIn':
9175 StopPropagationPreventDefault(e);
9176 if( ViewerZoomStart() ) { ViewerZoomIn( true ); }
9177 break;
9178 case 'zoomOut':
9179 StopPropagationPreventDefault(e);
9180 if( ViewerZoomStart() ) { ViewerZoomIn( false ); }
9181 break;
9182 case 'minimize':
9183 // toggle toolbar visibility
9184 StopPropagationPreventDefault(e);
9185 if( G.VOM.toolbarMode == 'std' ) {
9186 ViewerToolbarForVisibilityMin();
9187 }
9188 else {
9189 ViewerToolbarForVisibilityStd();
9190 }
9191 break;
9192 case 'fullScreen':
9193 // Toggle viewer fullscreen mode on/off
9194 e.stopPropagation();
9195 if( ngscreenfull.enabled ) {
9196 ngscreenfull.toggle();
9197 }
9198 break;
9199 case 'info':
9200 e.stopPropagation();
9201 ItemDisplayInfo( G.VOM.content.current.NGY2Item() );
9202 break;
9203 case 'close':
9204 StopPropagationPreventDefault(e);
9205 if( (new Date().getTime()) - G.VOM.timeImgChanged < 400 ) { return; }
9206 LightboxClose();
9207 break;
9208 case 'download':
9209 StopPropagationPreventDefault(e);
9210 DownloadImage(G.VOM.items[G.VOM.content.current.vIdx].ngy2ItemIdx);
9211 break;
9212 case 'share':
9213 StopPropagationPreventDefault(e);
9214 PopupShare(G.VOM.items[G.VOM.content.current.vIdx].ngy2ItemIdx);
9215 break;
9216 case 'linkOriginal':
9217 StopPropagationPreventDefault(e);
9218 OpenOriginal( G.VOM.content.current.NGY2Item() );
9219 break;
9220 case 'rotateLeft':
9221 StopPropagationPreventDefault(e);
9222 ViewerImageRotate(-90);
9223 break;
9224 case 'rotateRight':
9225 StopPropagationPreventDefault(e);
9226 ViewerImageRotate(90);
9227 break;
9228 case 'shoppingcart':
9229 StopPropagationPreventDefault(e);
9230 AddToCart( G.VOM.items[G.VOM.content.current.vIdx].ngy2ItemIdx, 'lightbox');
9231 break;
9232 }
9233
9234 // custom button
9235 var fu = G.O.fnImgToolbarCustClick;
9236 if( ngy2action.indexOf('custom') == 0 && fu !== null ) {
9237 typeof fu == 'function' ? fu(ngy2action, $this, G.VOM.content.current.NGY2Item() ) : window[fu](ngy2action, $this, G.VOM.content.current.NGY2Item() );
9238 }
9239 }
9240
9241 // rotate displayed image
9242 function ViewerImageRotate( angle ) {
9243 var item = G.VOM.content.current.NGY2Item();
9244 if( item.mediaKind == 'img' ) {
9245 item.rotationAngle += angle;
9246 item.rotationAngle = item.rotationAngle % 360;
9247 if( item.rotationAngle < 0 ) {
9248 item.rotationAngle += 360;
9249 }
9250 ViewerMediaPanX( 0 );
9251 ViewerMediaSetPosAndZoomOne( G.VOM.content.current, true );
9252 }
9253 }
9254
9255
9256 // Display photo infos in popup/modal
9257 function ItemDisplayInfo( ng2item ) {
9258
9259 var content = '<div class="nGY2PopupOneItem">' + ng2item.title + '</div>';
9260 content += '<div class="nGY2PopupOneItemText">' + ng2item.description + '</div>';
9261 if( ng2item.author != '' ) {
9262 content += '<div class="nGY2PopupOneItemText">' + G.O.icons.user + ' ' + ng2item.author + '</div>';
9263 }
9264 if( ng2item.exif.model != '' ) {
9265 content += '<div class="nGY2PopupOneItemText">' + G.O.icons.config + ' ' + ng2item.exif.model + '</div>';
9266 }
9267 var sexif = G.O.icons.picture + ':';
9268 if( ng2item.exif.flash != '' || ng2item.exif.focallength != '' || ng2item.exif.fstop != '' || ng2item.exif.exposure != '' || ng2item.exif.iso != '' || ng2item.exif.time != '' ) {
9269 sexif += '<br>';
9270 sexif += ng2item.exif.flash == '' ? '' : ' &nbsp; ' + ng2item.exif.flash;
9271 sexif += ng2item.exif.focallength == '' ? '' : ' &nbsp; ' + ng2item.exif.focallength+'mm';
9272 sexif += ng2item.exif.fstop == '' ? '' : ' &nbsp; f' + ng2item.exif.fstop;
9273 sexif += ng2item.exif.exposure == '' ? '' : ' &nbsp; ' + ng2item.exif.exposure+'s';
9274 sexif += ng2item.exif.iso == '' ? '' : ' &nbsp; ' + ng2item.exif.iso+' ISO';
9275 if( ng2item.exif.time != '' ) {
9276 // var date = new Date(parseInt(ng2item.exif.time));
9277 // sexif += ' &nbsp; '+date.toLocaleDateString();
9278 sexif += ' &nbsp; ' + ng2item.exif.time;
9279 }
9280 }
9281 else {
9282 sexif += ' n/a';
9283 }
9284 content += '<div class="nGY2PopupOneItemText">' + sexif + '</div>';
9285
9286 if( ng2item.exif.location != '' ) {
9287 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>';
9288 // embed google map in iframe (no api key required)
9289 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>';
9290 }
9291 else {
9292 content += '<div class="nGY2PopupOneItemText">' + G.O.icons.location + ': n/a</div>';
9293 }
9294
9295 var r = { title: G.O.icons.viewerInfo, content: content };
9296
9297 // callback
9298 var fu = G.O.fnPopupMediaInfo;
9299 if( fu !== null ) {
9300 typeof fu == 'function' ? r=fu(ng2item, r.title, r.content) : r=window[fu](ng2item, r.title, r.content);
9301 }
9302
9303
9304 Popup( r.title, r.content, 'Left');
9305
9306 }
9307
9308
9309
9310 function ToolbarAddElt( elt ) {
9311 var r = '<div class="ngbt ngy2viewerToolAction ',
9312 e=elt.replace(/^\s+|\s+$/g, ''); // remove trailing/leading whitespace
9313 switch( e ) {
9314 case 'minimizeButton':
9315 case 'minimize':
9316 var ic = G.O.icons.viewerToolbarMin;
9317 if( G.VOM.toolbarMode == 'min' ) {
9318 ic = G.O.icons.viewerToolbarStd;
9319 }
9320 r += 'minimizeButton nGEvent" data-ngy2action="minimize">'+ic+'</div>';
9321 break;
9322 case 'previousButton':
9323 case 'previous':
9324 r += 'previousButton nGEvent" data-ngy2action="previous">'+ G.O.icons.viewerPrevious +'</div>';
9325 break;
9326 case 'pageCounter':
9327 r += 'pageCounter nGEvent"></div>';
9328 break;
9329 case 'nextButton':
9330 case 'next':
9331 r += 'nextButton nGEvent" data-ngy2action="next">'+ G.O.icons.viewerNext +'</div>';
9332 break;
9333 case 'playPauseButton':
9334 case 'playPause':
9335 r += 'playButton playPauseButton nGEvent" data-ngy2action="playPause">'+ G.O.icons.viewerPlay +'</div>';
9336 break;
9337 case 'rotateLeft':
9338 r += 'rotateLeftButton nGEvent" data-ngy2action="rotateLeft">'+ G.O.icons.viewerRotateLeft +'</div>';
9339 break;
9340 case 'rotateRight':
9341 r += 'rotateRightButton nGEvent" data-ngy2action="rotateRight">'+ G.O.icons.viewerRotateRight +'</div>';
9342 break;
9343 case 'downloadButton':
9344 case 'download':
9345 r += 'downloadButton nGEvent" data-ngy2action="download">'+ G.O.icons.viewerDownload +'</div>';
9346 break;
9347 case 'zoomButton':
9348 case 'zoom':
9349 r += 'nGEvent" data-ngy2action="zoomIn">'+ G.O.icons.viewerZoomIn +'</div><div class="ngbt ngy2viewerToolAction nGEvent" data-ngy2action="zoomOut">'+ G.O.icons.viewerZoomOut +'</div>';
9350 break;
9351 case 'fullscreenButton':
9352 case 'fullscreen':
9353 var s = G.O.icons.viewerFullscreenOn;
9354 if( ngscreenfull.enabled && G.VOM.viewerIsFullscreen ) {
9355 s = G.O.icons.viewerFullscreenOff;
9356 }
9357 r += 'setFullscreenButton fullscreenButton nGEvent" data-ngy2action="fullScreen">'+s+'</div>';
9358 break;
9359 case 'infoButton':
9360 case 'info':
9361 r += 'infoButton nGEvent" data-ngy2action="info">'+ G.O.icons.viewerInfo +'</div>';
9362 break;
9363 case 'linkOriginalButton':
9364 case 'linkOriginal':
9365 r += 'linkOriginalButton nGEvent" data-ngy2action="linkOriginal">' + G.O.icons.viewerLinkOriginal + '</div>';
9366 break;
9367 case 'closeButton':
9368 case 'close':
9369 r += 'closeButton nGEvent" data-ngy2action="close">'+ G.O.icons.buttonClose +'</div>';
9370 break;
9371 case 'shareButton':
9372 case 'share':
9373 r += 'nGEvent" data-ngy2action="share">'+ G.O.icons.viewerShare +'</div>';
9374 break;
9375 case 'label':
9376 r += '"><div class="label"><div class="title nGEvent" itemprop="name"></div><div class="description nGEvent" itemprop="description"></div></div></div>';
9377 break;
9378 case 'shoppingcart':
9379 r += 'closeButton nGEvent" data-ngy2action="shoppingcart">'+ G.O.icons.viewerShoppingcart +'</div>';
9380 break;
9381 default:
9382 // custom button
9383 if( e.indexOf('custom') == 0 ) {
9384 var t = '';
9385 // content to display from custom script
9386 var fu = G.O.fnImgToolbarCustInit;
9387 if( fu !== null ) {
9388 typeof fu == 'function' ? fu(e) : window[fu](e);
9389 }
9390 if( t == undefined || t == '' ) {
9391 // content from icons
9392 var n = e.substring(6);
9393 t = G.O.icons['viewerCustomTool'+n];
9394 }
9395 r += 'ngy2CustomBtn ' + e + ' nGEvent" data-ngy2action="' + e + '">' + t + '</div>';
9396 }
9397 else {
9398 r = '';
9399 }
9400 break;
9401 }
9402 return r;
9403 }
9404
9405
9406 // toggle slideshow mode on/off
9407 function SlideshowToggle(){
9408 if( G.VOM.playSlideshow ) {
9409 window.clearTimeout(G.VOM.playSlideshowTimerID);
9410 G.VOM.playSlideshow = false;
9411 G.VOM.$viewer.find('.playPauseButton').html(G.O.icons.viewerPlay);
9412 }
9413 else {
9414 G.VOM.playSlideshow = true;
9415 DisplayNextMedia();
9416 G.VOM.$viewer.find('.playPauseButton').html(G.O.icons.viewerPause);
9417 }
9418 }
9419
9420 function ViewerToolbarForVisibilityStd() {
9421 G.VOM.toolbarMode = 'std';
9422
9423 var sTB = '';
9424 var t = G.O.viewerToolbar.standard.split(',');
9425 for( var i = 0, lt = t.length; i < lt; i++) {
9426 sTB += ToolbarAddElt( t[i] );
9427 }
9428 G.VOM.$toolbar.find('.toolbar').html(sTB);
9429 ViewerToolbarElementContent();
9430 }
9431
9432 function ViewerToolbarForVisibilityMin() {
9433 if( G.O.viewerToolbar.minimized == undefined || G.O.viewerToolbar.minimized == '' ) {
9434 ViewerToolbarForVisibilityStd();
9435 }
9436 else {
9437 G.VOM.toolbarMode = 'min';
9438 var sTB = '';
9439 var t = G.O.viewerToolbar.minimized.split(',');
9440 for( var i = 0, lt = t.length; i < lt; i++) {
9441 sTB += ToolbarAddElt( t[i] );
9442 }
9443 G.VOM.$toolbar.find('.toolbar').html(sTB);
9444 ViewerToolbarElementContent();
9445 }
9446 }
9447
9448 function ViewerToolbarElementContent() {
9449
9450 var vomIdx = G.VOM.content.current.vIdx;
9451 if( vomIdx == null ) { return; }
9452
9453 var item = G.VOM.content.current.NGY2Item();
9454
9455 // LABEL
9456 var setTxt = false;
9457 // set title
9458 if( item.title !== undefined && item.title != '' ) {
9459 G.VOM.$viewer.find('.ngy2viewerToolAction').find('.title').html(item.title);
9460 setTxt = true;
9461 }
9462 else {
9463 G.VOM.$viewer.find('.ngy2viewerToolAction').find('.title').html('');
9464 }
9465 // set description
9466 if( item.description !== undefined && item.description != '' ) {
9467 G.VOM.$viewer.find('.ngy2viewerToolAction').find('.description').html(item.description);
9468 setTxt = true;
9469 }
9470 else {
9471 G.VOM.$viewer.find('.ngy2viewerToolAction').find('.description').html('');
9472 }
9473
9474 if( setTxt ) {
9475 G.VOM.$viewer.find('.ngy2viewerToolAction').find('.label').show();
9476 }
9477 else {
9478 G.VOM.$viewer.find('.ngy2viewerToolAction').find('.label').hide();
9479 }
9480
9481 // set page number
9482 var viewerMaxImages = G.VOM.items.length;
9483 if( viewerMaxImages > 0 ) {
9484 G.VOM.$viewer.find('.pageCounter').html((G.VOM.items[vomIdx].mediaNumber)+'/'+viewerMaxImages);
9485 }
9486
9487 // custom elements
9488 var $cu = G.VOM.$viewer.find('.ngy2CustomBtn');
9489 var fu = G.O.fnImgToolbarCustDisplay;
9490 if( $cu.length > 0 && fu !== null ) {
9491 typeof fu == 'function' ? fu($cu, item) : window[fu]($cu, item);
9492 }
9493
9494 // set event handlers again
9495 ViewerToolsOn();
9496 }
9497
9498 // Pan the media container in the lightbox (left/right)
9499 function ViewerMediaPanX( posX ) {
9500 G.VOM.swipePosX = posX;
9501 if( G.CSStransformName == null ) {
9502 // no pan if CSS transform not supported
9503 // G.VOM.$mediaCurrent.css({ left: posX });
9504 }
9505 else {
9506
9507 // pan left/right the current media
9508 // window.ng_draf( function() {
9509 G.VOM.content.current.$media[0].style[G.CSStransformName] = 'translate(' + posX + 'px, 0px)';
9510 // });
9511
9512 var itemPrevious = G.VOM.content.previous.NGY2Item();
9513 var itemNext = G.VOM.content.next.NGY2Item();
9514
9515 // next/previous media
9516 if( G.O.imageTransition.startsWith('SWIPE') ) {
9517 if( itemPrevious.mediaTransition() ) {
9518 ViewerSetMediaVisibility(G.VOM.content.previous, 1);
9519 }
9520 if( itemNext.mediaTransition() ) {
9521 ViewerSetMediaVisibility(G.VOM.content.next, 1);
9522 }
9523
9524 var sc = Math.min( Math.max( Math.abs(posX) / G.VOM.window.lastWidth, .8), 1);
9525 if( G.O.imageTransition == 'SWIPE' ) { sc = 1; }
9526
9527 if( posX > 0 ) {
9528 let dir = G.VOM.window.lastWidth;
9529 if( itemPrevious.mediaTransition() ) {
9530 // window.ng_draf( function() {
9531 G.VOM.content.previous.$media[0].style[G.CSStransformName] = 'translate(' + (-dir + posX) + 'px, 0px) scale(' + sc + ')';
9532 // });
9533 }
9534 if( itemNext.mediaTransition() ) {
9535 // window.ng_draf( function() {
9536 G.VOM.content.next.$media[0].style[G.CSStransformName] = 'translate(' + (dir) + 'px, 0px) scale(' + sc + ')';
9537 // });
9538 }
9539 }
9540 else {
9541 let dir = -G.VOM.window.lastWidth;
9542 if( itemNext.mediaTransition() ) {
9543 // window.ng_draf( function() {
9544 G.VOM.content.next.$media[0].style[G.CSStransformName] = 'translate(' + (-dir + posX) + 'px, 0px) scale(' + sc + ')';
9545 // });
9546 }
9547 if( itemPrevious.mediaTransition() ) {
9548 // window.ng_draf( function() {
9549 G.VOM.content.previous.$media[0].style[G.CSStransformName] = 'translate(' + (dir) + 'px, 0px) scale(' + sc + ')';
9550 // });
9551 }
9552 }
9553 }
9554
9555
9556 if( G.O.imageTransition == 'SLIDEAPPEAR' ) {
9557 G.VOM.content.previous.$media[0].style[G.CSStransformName] = '';
9558 G.VOM.content.next.$media[0].style[G.CSStransformName] = '';
9559 if( posX < 0 ) {
9560 let o = (-posX) / G.VOM.window.lastWidth;
9561 if( itemNext.mediaTransition() ) {
9562 ViewerSetMediaVisibility(G.VOM.content.next, o);
9563 }
9564 if( itemPrevious.mediaTransition() ) {
9565 ViewerSetMediaVisibility(G.VOM.content.previous, 0);
9566 }
9567 }
9568 else {
9569 let o = posX / G.VOM.window.lastWidth;
9570 if( itemPrevious.mediaTransition() ) {
9571 ViewerSetMediaVisibility(G.VOM.content.previous, o);
9572 }
9573 if( itemNext.mediaTransition() ) {
9574 ViewerSetMediaVisibility(G.VOM.content.next, 0);
9575 }
9576 }
9577 }
9578 }
9579 }
9580
9581 // Display next image
9582 function DisplayNextMedia( velocity ) {
9583 velocity = velocity || 0;
9584
9585 if( G.VOM.viewerMediaIsChanged || ((new Date().getTime()) - G.VOM.timeImgChanged < 300) ) { return; }
9586
9587 TriggerCustomEvent('lightboxNextImage');
9588 LightboxDisplay('nextImage', velocity);
9589 };
9590
9591 // Display previous image
9592 function DisplayPreviousMedia( velocity ) {
9593 velocity = velocity || 0;
9594
9595 if( G.VOM.viewerMediaIsChanged || ((new Date().getTime()) - G.VOM.timeImgChanged < 300) ) { return; }
9596 if( G.VOM.playSlideshow ) {
9597 SlideshowToggle();
9598 }
9599
9600 TriggerCustomEvent('lightboxPreviousImage');
9601 LightboxDisplay( 'previousImage', velocity);
9602 };
9603
9604
9605
9606 // Display image (with animation if possible)
9607 function LightboxDisplay( displayType, velocity ) {
9608
9609 velocity = velocity || 0;
9610
9611 if( G.O.debugMode && console.timeline ) { console.timeline('nanogallery2_viewer'); }
9612
9613 if( G.VOM.playSlideshow ) { window.clearTimeout( G.VOM.playSlideshowTimerID ); }
9614
9615 var current_content_item = null;
9616 var new_content_item = null;
9617
9618 G.VOM.timeImgChanged = new Date().getTime();
9619 G.VOM.viewerMediaIsChanged = true;
9620 G.VOM.zoom.isZooming = false;
9621 ResizeLightbox(true);
9622
9623 switch( displayType ) {
9624 case '':
9625 current_content_item = G.VOM.content.current;
9626 new_content_item = G.VOM.content.current;
9627 break;
9628 case 'previousImage':
9629 current_content_item = G.VOM.content.current;
9630 new_content_item = G.VOM.content.previous;
9631 break;
9632 default:
9633 current_content_item = G.VOM.content.current;
9634 new_content_item = G.VOM.content.next;
9635 }
9636
9637 // SetLocationHash( next_ng2item.albumID, next_ng2item.GetID() );
9638 SetLocationHash( new_content_item.NGY2Item().albumID, new_content_item.NGY2Item().GetID() );
9639
9640 if( displayType == '' ) {
9641 // first media -> no transition -> exit
9642 return;
9643 }
9644
9645 // animation duration is proportional of the remaining distance
9646 var vP = G.GOM.cache.viewport;
9647 var t_easing = '';
9648 var t_dur = 500 * (vP.w - Math.abs(G.VOM.swipePosX)) / vP.w;
9649 if( velocity > 0 ) {
9650 // velocity = pixels/millisecond
9651 t_dur = Math.min( (vP.w - Math.abs(G.VOM.swipePosX)) / velocity, t_dur);
9652 t_easing = 'linear'; // use linear to avoid a slow-down in the transition after user swipe
9653 }
9654
9655
9656 // animate the image transition between 2 medias
9657
9658 if( G.CSStransformName == null ) {
9659 // no CSS transform support -> no animation
9660 ViewerSetMediaVisibility(new_content_item, 1);
9661 ViewerSetMediaVisibility(current_content_item, 1);
9662 LightboxDisplayFinalize(displayType);
9663 }
9664 else {
9665 switch( G.O.imageTransition ) {
9666 case 'SWIPE':
9667 case 'SWIPE2':
9668 var dir = ( displayType == 'nextImage' ? - vP.w : vP.w );
9669 new_content_item.$media[0].style[G.CSStransformName] = 'translate(' + (-dir) + 'px, 0px) '
9670
9671 if( velocity == 0 ) {
9672 t_easing = G.O.imageTransition == 'swipe' ? 'easeInOutSine' : 'easeInQuart';
9673 }
9674
9675 ViewerSetMediaVisibility(G.VOM.content.current, 1);
9676 G.VOM.content.current.$media[0].style[G.CSStransformName] = 'translate(0px, 0px)';
9677 ViewerSetMediaVisibility(new_content_item, 1);
9678
9679 new NGTweenable().tween({
9680 from: { t: G.VOM.swipePosX },
9681 to: { t: (displayType == 'nextImage' ? - vP.w : vP.w) },
9682 attachment: { dT: displayType, new_content_item: new_content_item, dir: dir, media_transition: new_content_item.NGY2Item().mediaTransition()},
9683 // delay: 30,
9684 duration: t_dur,
9685 easing: ( t_easing ),
9686 step: function (state, att) {
9687 // current displayed media
9688 G.VOM.content.current.$media[0].style[G.CSStransformName] = 'translate(' + state.t + 'px, 0px)';
9689
9690 // new media
9691 if( att.media_transition ) {
9692 // new media supports transition
9693 var sc = Math.min( Math.max( (Math.abs(state.t)) / G.VOM.window.lastWidth, .8), 1);
9694 if( G.O.imageTransition == 'SWIPE' ) { sc = 1; }
9695 att.new_content_item.$media[0].style[G.CSStransformName] = 'translate(' + (-att.dir+state.t) + 'px, 0px) scale(' + sc + ')';
9696 }
9697 },
9698 finish: function (state, att) {
9699 G.VOM.content.current.$media[0].style[G.CSStransformName] = '';
9700 ViewerSetMediaVisibility(G.VOM.content.current, 0);
9701 att.new_content_item.$media[0].style[G.CSStransformName] = '';
9702 LightboxDisplayFinalize(att.dT);
9703 }
9704 });
9705 break;
9706
9707 case 'SLIDEAPPEAR':
9708 default:
9709 // var dir=(displayType == 'nextImage' ? - vP.w : vP.w);
9710 var op = (Math.abs(G.VOM.swipePosX)) / G.VOM.window.lastWidth;
9711 new_content_item.$media[0].style[G.CSStransformName] = '';
9712 if( velocity == 0 ) {
9713 t_easing ='easeInOutSine';
9714 }
9715 new NGTweenable().tween({
9716 from: { o: op, t: G.VOM.swipePosX },
9717 to: { o: 1, t: (displayType == 'nextImage' ? - vP.w : vP.w) },
9718 attachment: { dT: displayType, new_content_item:new_content_item, media_transition: new_content_item.NGY2Item().mediaTransition() },
9719 delay: 30,
9720 duration: t_dur,
9721 easing: t_easing,
9722 step: function (state, att) {
9723 // current media - translate
9724 G.VOM.content.current.$media[0].style[G.CSStransformName]= 'translate('+state.t+'px, 0px)';
9725
9726 // new media - opacity
9727 if( att.media_transition ) {
9728 // new media supports transition
9729 ViewerSetMediaVisibility(att.new_content_item, state.o);
9730 }
9731 },
9732 finish: function (state, att) {
9733 G.VOM.content.current.$media[0].style[G.CSStransformName]= '';
9734 LightboxDisplayFinalize(att.dT);
9735 }
9736 });
9737 break;
9738 }
9739 }
9740
9741 }
9742
9743
9744 function LightboxDisplayFinalize( displayType ) {
9745
9746 var newVomIdx = 0;
9747 switch( displayType ) {
9748 case '':
9749 // first media to display in lightbox
9750 newVomIdx = G.VOM.content.current.vIdx;
9751 break;
9752 case 'previousImage':
9753 // previous media
9754 newVomIdx = G.VOM.content.previous.vIdx;
9755 break;
9756 default:
9757 // next media
9758 newVomIdx = G.VOM.content.next.vIdx;
9759 }
9760
9761
9762
9763 G.VOM.content.current.vIdx = newVomIdx;
9764 G.VOM.content.next.vIdx = G.VOM.IdxNext();
9765 G.VOM.content.previous.vIdx = G.VOM.IdxPrevious();
9766 G.VOM.gallery.Resize();
9767 G.VOM.gallery.SetThumbnailActive();
9768
9769 var ngy2item = G.VOM.content.current.NGY2Item();
9770
9771 ViewerToolbarElementContent();
9772 if( G.O.debugMode && console.timeline ) { console.timelineEnd('nanogallery2_viewer'); }
9773
9774 var fu=G.O.fnImgDisplayed;
9775 if( fu !== null ) {
9776 typeof fu == 'function' ? fu(ngy2item) : window[fu](ngy2item);
9777 }
9778
9779 G.VOM.swipePosX = 0;
9780 if( displayType != '' ) {
9781 // not on first media display
9782 G.VOM.content.current.$media.removeClass('imgCurrent');
9783
9784 var $tmp = G.VOM.content.current.$media;
9785 switch( displayType ) {
9786 case 'nextImage':
9787 G.VOM.content.current.$media = G.VOM.content.next.$media;
9788 G.VOM.content.next.$media = $tmp;
9789 break;
9790 case 'previousImage':
9791 G.VOM.content.current.$media = G.VOM.content.previous.$media;
9792 G.VOM.content.previous.$media = $tmp;
9793 break;
9794 }
9795 }
9796
9797 G.VOM.content.current.$media.addClass('imgCurrent');
9798
9799 // re-sort the media containers --> current on top
9800 var $pans = G.VOM.$content.find('.nGY2ViewerMediaPan');
9801 G.VOM.content.current.$media.insertAfter($pans.last());
9802
9803 if( ngy2item.mediaKind == 'img' && ngy2item.imageWidth == 0 ) {
9804 ViewerSetMediaVisibility(G.VOM.content.current, 0);
9805 }
9806 else {
9807 G.VOM.content.current.$media.children().eq(0).attr('class', 'nGY2ViewerMediaLoaderHidden'); // hide preloader
9808 ViewerSetMediaVisibility(G.VOM.content.current, 1);
9809 }
9810
9811
9812 // set the new NEXT media
9813 G.VOM.content.next.$media.empty();
9814 var nextItem = G.VOM.content.next.NGY2Item();
9815 var spreloader = '<div class="nGY2ViewerMediaLoaderDisplayed"></div>';
9816 if( nextItem.mediaKind == 'img' && nextItem.imageWidth != 0 && nextItem.imageHeight != 0 ) {
9817 spreloader = '<div class="nGY2ViewerMediaLoaderHidden"></div>';
9818 }
9819 G.VOM.content.next.$media.append( spreloader + nextItem.mediaMarkup );
9820 ViewerSetMediaVisibility(G.VOM.content.next, 0);
9821 ViewerSetMediaVisibility(G.VOM.content.previous, 0);
9822 if( nextItem.mediaKind == 'img' ) {
9823 G.VOM.ImageLoader.loadImage(VieweImgSizeRetrieved, nextItem);
9824 }
9825 else {
9826 ViewerMediaCenterNotImg( G.VOM.content.next.$media );
9827 }
9828
9829 // set the new PREVIOUS media
9830 G.VOM.content.previous.$media.empty();
9831 var previousItem = G.VOM.content.previous.NGY2Item();
9832 spreloader = '<div class="nGY2ViewerMediaLoaderDisplayed"></div>';
9833 if( previousItem.mediaKind == 'img' && previousItem.imageWidth != 0 && previousItem.imageHeight != 0 ) {
9834 spreloader = '<div class="nGY2ViewerMediaLoaderHidden"></div>';
9835 }
9836 G.VOM.content.previous.$media.append( spreloader + previousItem.mediaMarkup );
9837 ViewerSetMediaVisibility(G.VOM.content.previous, 0);
9838 ViewerSetMediaVisibility(G.VOM.content.next, 0);
9839 if( previousItem.mediaKind == 'img' ) {
9840 G.VOM.ImageLoader.loadImage( VieweImgSizeRetrieved, previousItem );
9841 }
9842 else {
9843 ViewerMediaCenterNotImg( G.VOM.content.previous.$media );
9844 }
9845
9846
9847 // slideshow mode - wait until image is loaded to start the delay for next image
9848 if( G.VOM.playSlideshow ) {
9849 G.VOM.content.current.$media.children().eq(1).ngimagesLoaded().always( function( instance ) {
9850 if( G.VOM.playSlideshow ) {
9851 // in the meantime the user could have stopped the slideshow
9852 G.VOM.playSlideshowTimerID = window.setTimeout( function(){ DisplayNextMedia(); }, G.VOM.slideshowDelay );
9853 }
9854 });
9855 }
9856
9857 // close viewer when user clicks outside of the image
9858 // G.VOM.$mediaCurrent.on("click",function(e){
9859 // e.stopPropagation();
9860 // if( (new Date().getTime()) - G.VOM.timeImgChanged < 400 ) { return; }
9861 // StopPropagationPreventDefault(e);
9862 // LightboxClose(G.VOM.currItemIdx);
9863 // return false;
9864 // });
9865
9866 ResizeLightbox();
9867
9868 G.VOM.viewerMediaIsChanged = false;
9869 TriggerCustomEvent('lightboxImageDisplayed');
9870
9871 }
9872
9873
9874 // Is fired as soon as the size of an image has been retrieved (the download may not be finished)
9875 function VieweImgSizeRetrieved(w, h, item, n) {
9876
9877 item.imageWidth = w;
9878 item.imageHeight = h;
9879
9880 // image sized retrieved for currently displayed media
9881 if( G.VOM.content.current.NGY2Item() == item ) {
9882 // it is the current displayed media
9883 G.VOM.content.current.$media.children().eq(0).attr('class', 'nGY2ViewerMediaLoaderHidden'); // hide preloader
9884 ViewerSetMediaVisibility(G.VOM.content.current, 1);
9885 G.VOM.zoom.userFactor = 1;
9886 }
9887
9888 if( G.VOM.content.next.NGY2Item() == item ) { // next media
9889 G.VOM.content.next.$media.children().eq(0).attr('class', 'nGY2ViewerMediaLoaderHidden'); // hide preloader
9890 }
9891 if( G.VOM.content.previous.NGY2Item() == item ) { // previous media
9892 G.VOM.content.previous.$media.children().eq(0).attr('class', 'nGY2ViewerMediaLoaderHidden'); // hide preloader
9893 }
9894
9895 ViewerMediaSetPosAndZoom();
9896
9897 }
9898
9899 // Viewer - Set the visibility of the media and it's container
9900 // function ViewerSetMediaVisibility(item, $media, opacity ) {
9901 function ViewerSetMediaVisibility( content_item, opacity ) {
9902
9903 var item = content_item.NGY2Item();
9904 var $media = content_item.$media;
9905
9906 if( item.mediaKind == 'img' && item.imageWidth == 0 ) {
9907 // do not display image if width is unknown (--> callback will set the width when know)
9908 // setting opacity to 0 is not enough -> it is mandatory to change also the visibility to hidden to avoid responds to events (click/touch)
9909 // $media.children().css({ opacity: 0, visibility: 'hidden' });
9910 $media.children().eq(1).css({ opacity: 0, visibility: 'hidden' }); // hide media
9911 // $media.css({ opacity: 0, visibility: 'hidden' });
9912 return;
9913 }
9914
9915 if( opacity == 0 ) {
9916 // setting opacity to 0 is not enough -> it is mandatory to change also the visibility to hidden to avoid responds to events (click/touch)
9917 // $media.css({ opacity: 0, visibility: 'hidden' });
9918 $media.children().css({ opacity: 0, visibility: 'hidden' }); // hide media
9919 }
9920 else {
9921 // $media.css({ opacity: opacity, visibility: 'visible' });
9922 $media.children().css({ opacity: opacity, visibility: 'visible' }); // display media
9923 }
9924 }
9925
9926
9927 // Close the internal lightbox
9928 function LightboxClose( vomIdx ) {
9929
9930
9931 if( vomIdx == undefined ) {
9932 vomIdx = G.VOM.content.current.vIdx;
9933 }
9934
9935 G.VOM.viewerMediaIsChanged = false;
9936
9937 if( G.VOM.viewerDisplayed ) {
9938
9939 // set scrollbar visible again
9940 jQuery("body").removeClass("nGY2_body_scrollbar");
9941 jQuery("#nGY2_body_scrollbar_style").remove();
9942
9943 if( G.VOM.playSlideshow ) {
9944 window.clearTimeout( G.VOM.playSlideshowTimerID );
9945 G.VOM.playSlideshow = false;
9946 }
9947
9948 // G.VOM.userEvents.removeEventListeners();
9949 // G.VOM.userEvents=null;
9950 G.VOM.hammertime.destroy();
9951 G.VOM.hammertime = null;
9952
9953 if( ngscreenfull.enabled && G.VOM.viewerIsFullscreen ) {
9954 G.VOM.viewerIsFullscreen = false;
9955 ngscreenfull.exit();
9956 }
9957
9958 G.VOM.$baseCont.hide(0).off().show(0).html('').remove();
9959 G.VOM.viewerDisplayed = false;
9960
9961 if( G.O.lightboxStandalone ) { return; }
9962
9963 if( G.O.thumbnailAlbumDisplayImage ) {
9964 // content of album displayed directly in lightbox (no gallery display for album content)
9965 if( vomIdx == null ) {
9966 // lightbox closed with browser back-button
9967 // the gallery is already displayed
9968 }
9969 else {
9970 var item = G.I[G.VOM.items[vomIdx].ngy2ItemIdx];
9971 var parent = NGY2Item.Get(G, item.albumID);
9972 if( G.GOM.albumIdx != parent.albumID ) {
9973 // display album only if not already displayed
9974 DisplayAlbum('-1', parent.albumID);
9975 }
9976 else {
9977 GalleryResize();
9978 SetLocationHash( '', '' );
9979 ThumbnailHoverReInitAll();
9980 }
9981 }
9982 // DisplayAlbum( '-', G.I[G.VOM.items[vomIdx].ngy2ItemIdx].albumID );
9983 }
9984 else {
9985 if( vomIdx != null ) {
9986 if( G.GOM.albumIdx == -1 ) {
9987 // album not displayed --> display gallery
9988 DisplayAlbum( '', G.I[G.VOM.items[vomIdx].ngy2ItemIdx].albumID );
9989 }
9990 else {
9991 GalleryResize();
9992 SetLocationHash( G.I[G.VOM.items[vomIdx].ngy2ItemIdx].albumID, '' );
9993 ThumbnailHoverReInitAll();
9994 }
9995 }
9996 }
9997 G.VOM.timeImgChanged = new Date().getTime();
9998 }
9999 }
10000
10001
10002 // Lightbox resized -> reposition elements
10003 function ResizeLightbox( forceUpdate ) {
10004 forceUpdate = typeof forceUpdate !== 'undefined' ? forceUpdate : false;
10005
10006 if( G.VOM.$toolbar === null ) { return; } // viewer build not finished
10007
10008
10009 // window.requestAnimationFrame( function() { // synchronize with screen
10010 var windowsW = G.VOM.$viewer.width();
10011 var windowsH = G.VOM.$viewer.height();
10012 var $elt = G.VOM.content.current.$media.children().eq(1);
10013 if( $elt == null || G.VOM.content.current.vIdx == -1 ) { return; }
10014
10015 if( !forceUpdate && G.VOM.window.lastWidth == windowsW && G.VOM.window.lastHeight == windowsH ) { return; }
10016
10017 G.VOM.window.lastWidth = windowsW;
10018 G.VOM.window.lastHeight = windowsH;
10019
10020 // var $tb = G.VOM.$toolbar.find('.toolbar');
10021 // var tb_OHt = $tb.outerHeight(true);
10022
10023
10024 var galleryHeight = 0;
10025 var cBottom = 0;
10026 // Height of the thumbnails gallery
10027 if( G.O.viewerGallery != 'none' ) {
10028 galleryHeight = G.O.viewerGalleryTHeight + 10;
10029 }
10030 if( G.O.viewerGallery == 'bottom' ) {
10031 cBottom = galleryHeight;
10032 }
10033
10034
10035 switch( G.O.viewerToolbar.position ) {
10036 case 'top':
10037 case 'topOverImage':
10038 G.VOM.$content.css({height: windowsH, width: windowsW, top: 0 });
10039 G.VOM.$toolbar.css({top: 0, bottom: ''});
10040 break;
10041 // case 'top':
10042 // windowsH -= tb_OHt;
10043 // G.VOM.$content.css({height: windowsH, width: windowsW, top: tb_OHt });
10044 // G.VOM.$toolbar.css({top: 0});
10045 // break;
10046 case 'bottom':
10047 case 'bottomOverImage':
10048 default:
10049 windowsH -= cBottom;
10050 G.VOM.$content.css({height: windowsH, width: windowsW, bottom: -cBottom, top: 0 });
10051 G.VOM.$toolbar.css({bottom: galleryHeight});
10052 break;
10053 // case 'bottom':
10054 // default:
10055 // windowsH -= tb_OHt;
10056 // G.VOM.$content.css({ width: windowsW, top: 0, bottom: tb_OHt });
10057 // G.VOM.$toolbar.css({bottom: galleryHeight});
10058 // break;
10059 }
10060
10061
10062 if( !G.VOM.viewerMediaIsChanged && G.VOM.zoom.isZooming ) {
10063 ViewerMediaSetPosAndZoom();
10064 }
10065 else {
10066 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 )) {
10067 // animate image zoom factor and position back to initial values
10068 G.VOM.zoom.isZooming= true; // activate zooming temporarily
10069 new NGTweenable().tween({
10070 from: { userFactor: G.VOM.zoom.userFactor, panPosX: G.VOM.panPosX, panPosY: G.VOM.panPosY, zoomPosX: G.VOM.zoom.posX, zoomPosY: G.VOM.zoom.posY },
10071 to: { userFactor: 1, panPosX: 0, panPosY: 0, zoomPosX: 0, zoomPosY: 0 },
10072 easing: 'easeInOutSine',
10073 delay: 0,
10074 duration: 150,
10075 step: function (state) {
10076 G.VOM.zoom.userFactor = state.userFactor;
10077 G.VOM.panPosX = state.panPosX;
10078 G.VOM.panPosY = state.panPosY;
10079 G.VOM.zoom.posX = state.zoomPosX;
10080 G.VOM.zoom.posY = state.zoomPosY;
10081 ViewerMediaSetPosAndZoom();
10082 },
10083 finish: function (state) {
10084 G.VOM.zoom.isZooming=false;
10085 }
10086 });
10087
10088 }
10089 else {
10090 G.VOM.zoom.userFactor = 1;
10091 G.VOM.zoom.isZooming = false;
10092 G.VOM.panPosX = 0;
10093 G.VOM.panPosY = 0;
10094 G.VOM.zoom.posX = 0;
10095 G.VOM.zoom.posY = 0;
10096 ViewerMediaSetPosAndZoom();
10097 }
10098 }
10099 }
10100
10101 // Retrieve the first parent element which is scrollable
10102 // source: ncubica - https://stackoverflow.com/questions/35939886/find-first-scrollable-parent
10103 // returns null if nothing found
10104 function getScrollableParent (node) {
10105 const regex = /(auto|scroll)/;
10106 const parents = (_node, ps) => {
10107 if (_node.parentNode === null) { return ps; }
10108 return parents(_node.parentNode, ps.concat([_node]));
10109 };
10110
10111 const style = (_node, prop) => getComputedStyle(_node, null).getPropertyValue(prop);
10112 const overflow = _node => style(_node, 'overflow') + style(_node, 'overflow-y') + style(_node, 'overflow-x');
10113 const scroll = _node => regex.test(overflow(_node));
10114
10115 const scrollParent = (_node) => {
10116 if (!(_node instanceof HTMLElement || _node instanceof SVGElement)) {
10117 return undefined;
10118 }
10119
10120 const ps = parents(_node.parentNode, []);
10121
10122 for (let i = 0; i < ps.length; i += 1) {
10123 if( ps[i] === document.body ) {
10124 return null;
10125 }
10126 if (scroll(ps[i])) {
10127 return ps[i];
10128 }
10129 }
10130
10131 return document.scrollingElement || document.documentElement;
10132 };
10133
10134 return scrollParent(node);
10135 };
10136
10137
10138
10139 /** @function BuildSkeleton */
10140 /** Build the gallery structure **/
10141 function BuildSkeleton() {
10142
10143
10144 // store markup if defined
10145 // var $elements = G.$E.base.children('a');
10146 var $elements = G.$E.base.children();
10147 if( $elements.length > 0 ) {
10148 G.O.$markup = $elements;
10149 }
10150
10151 if( !G.O.lightboxStandalone ) {
10152 G.$E.base.text('');
10153 G.$E.base.addClass('ngy2_container');
10154
10155 // RTL or LTR
10156 // var sRTL='';
10157 // if( G.O.RTL ) {
10158 // sRTL = 'style="text-align:right;direction:rtl;"';
10159 // }
10160
10161 // theme
10162 G.$E.base.addClass(G.O.theme)
10163 // gallery color scheme
10164 SetGalleryTheme();
10165
10166 // Hide icons (thumbnails and breadcrumb)
10167 if( G.O.thumbnailLabel.get('hideIcons') ) {
10168 G.O.icons.thumbnailAlbum = '';
10169 G.O.icons.thumbnailImage = '';
10170 }
10171
10172 // Navigation bar
10173 var styleNavigation="";
10174 if( G.O.navigationFontSize != undefined && G.O.navigationFontSize != '' ) {
10175 styleNavigation=' style="font-size:'+G.O.navigationFontSize+';"';
10176 }
10177 G.$E.conNavigationBar = jQuery('<div class="nGY2Navigationbar" '+styleNavigation+'></div>').appendTo(G.$E.base);
10178
10179 // pre-loader
10180 G.$E.conLoadingB = jQuery('<div class="nanoGalleryLBarOff"><div></div><div></div><div></div><div></div><div></div></div>').appendTo(G.$E.base);
10181
10182 // gallery
10183 G.$E.conTnParent = jQuery('<div class="nGY2Gallery"></div>').appendTo( G.$E.base );
10184 G.$E.conTn = jQuery('<div class="nGY2GallerySub"></div>').appendTo( G.$E.conTnParent );
10185
10186 // configure gallery
10187 switch( G.O.thumbnailAlignment ) {
10188 case 'left':
10189 G.$E.conTnParent.css({'text-align':'left'});
10190 // G.$E.conNavBCon.css({'margin-left':0 });
10191 break;
10192 case 'right':
10193 G.$E.conTnParent.css({'text-align':'right'});
10194 // G.$E.conNavBCon.css({ 'margin-right':0});
10195 break;
10196 }
10197
10198 // apply galleryBuildInit2 css settings to the gallery
10199 if( G.O.galleryBuildInit2 !== undefined ) {
10200 var t1=G.O.galleryBuildInit2.split('|');
10201 for( var i=0; i<t1.length; i++ ) {
10202 var o1=t1[i].split('_');
10203 if( o1.length == 2 ) {
10204 G.$E.conTn.css(o1[0], o1[1]);
10205 }
10206 }
10207 }
10208
10209 // configure gallery depending on some thumbnail hover effects
10210 var effects=G.tn.hoverEffects.std.concat(G.tn.hoverEffects.level1);
10211 for( var j=0; j<effects.length; j++) {
10212 switch( effects[j].type ) {
10213 case 'scale':
10214 case 'rotateZ':
10215 case 'rotateX':
10216 case 'rotateY':
10217 case 'translateX':
10218 case 'translateY':
10219 // handle some special cases
10220 if( effects[j].element == '.nGY2GThumbnail' ) {
10221 // allow thumbnail upscale over the gallery's aera
10222 G.$E.base.css('overflow', 'visible');
10223 G.$E.base.find('.nGY2GallerySub').css('overflow', 'visible');
10224 G.$E.conTnParent.css('overflow', 'visible');
10225 }
10226 break;
10227 }
10228 }
10229
10230 // Gallery bottom container
10231 G.$E.conTnBottom = jQuery('<div class="nGY2GalleryBottom" '+styleNavigation+'></div>').appendTo( G.$E.conTnParent );
10232
10233 // portable edition
10234 if( G.O.portable ) {
10235 // http://www.picresize.com/
10236 // http://base64encode.net/base64-image-encoder
10237 // 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';
10238 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';
10239 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;";
10240 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);
10241
10242 G.$E.ngy2i.find('a').on({
10243 mouseenter: function () {
10244 jQuery(this).attr('style', st);
10245 },
10246 mouseleave: function () {
10247 jQuery(this).attr('style', st);
10248 }
10249 });
10250 }
10251 }
10252
10253 // Error console
10254 G.$E.conConsole = jQuery('<div class="nGY2ConsoleParent"></div>').appendTo(G.$E.base);
10255
10256 // i18n translations
10257 i18n();
10258
10259 if( !G.O.lightboxStandalone ) {
10260 // cache some thumbnails data (sizes, styles...)
10261 ThumbnailDefCaches();
10262
10263 // do special settings depending for some options
10264 // thumbnail display transition
10265 switch( G.tn.opt.Get('displayTransition') ) {
10266 case 'SCALEDOWN':
10267 case 'RANDOMSCALE':
10268 default:
10269 G.$E.base.css('overflow', 'visible');
10270 G.$E.conTnParent.css('overflow', 'visible');
10271 G.$E.conTn.css('overflow', 'visible');
10272 break;
10273 }
10274 }
10275
10276 }
10277
10278 function TriggerCustomEvent ( eventName ) {
10279 // G.$E.base.trigger('pageChanged.nanogallery2', new Event('pageChanged.nanogallery2'));
10280 var eN = eventName + '.nanogallery2';
10281 var event=null;
10282 try {
10283 event = new Event( eN );
10284 } catch(e) {
10285 event = document.createEvent('Event');
10286 event.initEvent(eN, false, false);
10287 }
10288 G.$E.base.trigger(eN, event);
10289 }
10290
10291
10292 /** @function SetGlobalEvents */
10293 function SetGlobalEvents() {
10294 // GLOBAL EVENT MANAGEMENT
10295
10296 if( !G.O.lightboxStandalone ) {
10297 G.$E.conTnParent.on({
10298 mouseenter: GalleryMouseEnter,
10299 mouseleave: GalleryMouseLeave
10300 }, ".nGY2GThumbnail"); //pass the element as an argument to .on
10301
10302 // G.GOM.hammertime = new NGHammer(G.$E.conTn[0], { touchAction: 'none' });
10303 G.GOM.hammertime = new NGHammer( G.$E.conTn[0] );
10304 // G.GOM.hammertime.domEvents = true;
10305
10306
10307 // PAN on gallery (pagination)
10308 G.GOM.hammertime.on('pan', function(ev) {
10309 if( !G.VOM.viewerDisplayed ) {
10310 if( G.O.paginationSwipe && G.layout.support.rows && G.galleryDisplayMode.Get() == 'PAGINATION' ) {
10311 if( Math.abs(ev.deltaY) > G.GOM.panThreshold ) {
10312 G.GOM.panYOnly = true;
10313 }
10314 if( !G.GOM.panYOnly ) {
10315 G.$E.conTn.css( G.CSStransformName , 'translate('+(ev.deltaX)+'px,0px)');
10316 }
10317 }
10318 }
10319 });
10320 G.GOM.hammertime.on('panend', function(ev) {
10321 if( !G.VOM.viewerDisplayed ) {
10322 if( G.O.paginationSwipe && G.layout.support.rows && G.galleryDisplayMode.Get() == 'PAGINATION' ) {
10323 if( !G.GOM.panYOnly ) {
10324 if( ev.deltaX > 50 ) {
10325 paginationPreviousPage();
10326 return;
10327 }
10328 if( ev.deltaX < -50 ) {
10329 paginationNextPage();
10330 return;
10331 }
10332 }
10333 G.GOM.panYOnly = false;
10334 G.$E.conTn.css( G.CSStransformName , 'translate(0px,0px)');
10335 // pX=0;
10336 }
10337 }
10338 });
10339 // tap on gallery
10340 G.GOM.hammertime.on('tap', function(ev) {
10341 if( !G.VOM.viewerDisplayed ) {
10342 ev.srcEvent.stopPropagation();
10343 ev.srcEvent.preventDefault(); // cancel mouseenter event
10344
10345 if( ev.pointerType == 'mouse') {
10346 if( GalleryClicked(ev.srcEvent) == 'exit' ) { return; }
10347 }
10348 else {
10349 var r = GalleryEventRetrieveElementl(ev.srcEvent, false);
10350 if( r.GOMidx == -1 ) { return; }
10351 if( r.action != 'NONE' && r.action != 'OPEN' ) {
10352 // toolbar touched --> execute action
10353 GalleryClicked(ev.srcEvent);
10354 return;
10355 }
10356
10357 if( G.GOM.slider.hostIdx == r.GOMidx ) {
10358 // touch on thumbnail slider -> open immediately
10359 ThumbnailHoverOutAll();
10360 ThumbnailOpen(G.GOM.items[G.GOM.slider.currentIdx].thumbnailIdx, true);
10361 return;
10362 }
10363
10364 if( (G.GOM.curNavLevel == 'l1' && G.O.touchAnimationL1 == false) || (G.GOM.curNavLevel == 'lN' && G.O.touchAnimation == false) ) {
10365 // open on single touch (no hover animation)
10366 ThumbnailOpen(G.GOM.items[r.GOMidx].thumbnailIdx, true);
10367 return;
10368 }
10369
10370 if( G.O.touchAutoOpenDelay > 0 ) {
10371 // open on single touch after end of hover animation (=defined delay)
10372 ThumbnailHoverOutAll();
10373 ThumbnailHover( r.GOMidx );
10374 window.clearInterval( G.touchAutoOpenDelayTimerID );
10375 G.touchAutoOpenDelayTimerID = window.setInterval(function(){
10376 window.clearInterval( G.touchAutoOpenDelayTimerID );
10377 ThumbnailOpen( G.GOM.items[r.GOMidx].thumbnailIdx, true );
10378 }, G.O.touchAutoOpenDelay );
10379 }
10380 else {
10381 // two touch scenario
10382 if( !G.I[G.GOM.items[r.GOMidx].thumbnailIdx].hovered ) {
10383 ThumbnailHoverOutAll();
10384 ThumbnailHover(r.GOMidx);
10385 }
10386 else {
10387 // second touch
10388 ThumbnailOpen(G.GOM.items[r.GOMidx].thumbnailIdx, true);
10389 }
10390 }
10391 }
10392 }
10393 });
10394
10395 // browser location hash management
10396 if( G.O.locationHash ) {
10397 // jQuery(window).bind( 'hashchange', function() {
10398 // ProcessLocationHash();
10399 // });
10400 jQuery(window).on('hashchange.nanogallery2.' + G.baseEltID, function() {ProcessLocationHash();} );
10401 }
10402 }
10403
10404 // Page resize / orientation change
10405 jQuery(window).on('resize.nanogallery2.' + G.baseEltID + ' orientationChange.nanogallery2.' + G.baseEltID, debounce( ResizeWindowEvent, G.O.eventsDebounceDelay, false) );
10406
10407 // Event page scrolled
10408 jQuery(window).on('scroll.nanogallery2.' + G.baseEltID, debounce( OnScrollEvent, G.O.eventsDebounceDelay, false) );
10409
10410 if( !G.O.lightboxStandalone ) {
10411 // Scroll event on first scrollable parent element
10412 G.$E.scrollableParent = getScrollableParent( G.$E.base[0] );
10413 var sp = getScrollableParent( G.$E.base[0] );
10414 if( sp !== null ) {
10415 G.$E.scrollableParent = jQuery( sp );
10416 G.$E.scrollableParent.on('scroll.nanogallery2.' + G.baseEltID, debounce( OnScrollEvent, G.O.eventsDebounceDelay, false) );
10417 }
10418 }
10419
10420 // lightbox: hide tools/gallery after defined delay
10421 G.VOM.toolsHide = debounce( ViewerToolsHide, G.O.viewerHideToolsDelay, false );
10422
10423 // Keyboard management
10424 jQuery(document).keyup(function(e) {
10425 if( G.popup.isDisplayed ) {
10426 switch( e.keyCode) {
10427 case 27: // Esc key
10428 G.popup.close();
10429 break;
10430 }
10431 }
10432 else {
10433 if( G.VOM.viewerDisplayed ) {
10434 ViewerToolsUnHide();
10435 switch( e.keyCode) {
10436 case 27: // Escape key
10437 case 40: // DOWN
10438 case 38: // UP
10439 LightboxClose();
10440 break;
10441 case 32: // SPACE
10442 case 13: // ENTER
10443 SlideshowToggle();
10444 break;
10445 case 39: // RIGHT
10446 case 33: // PAGE UP
10447 DisplayNextMedia();
10448 break;
10449 case 37: // LEFT
10450 case 34: // PAGE DOWN
10451 DisplayPreviousMedia();
10452 break;
10453 case 35: // END
10454 case 36: // BEGIN
10455 }
10456 }
10457 }
10458 });
10459
10460 // mouse wheel to zoom in/out the image displayed in the internal lightbox
10461 jQuery(window).bind('mousewheel wheel', function(e){
10462 if( G.VOM.viewerDisplayed ) {
10463 var deltaY = 0;
10464 e.preventDefault();
10465
10466 if( ViewerZoomStart() ) {
10467 if (e.originalEvent.deltaY) { // FireFox 17+ (IE9+, Chrome 31+?)
10468 deltaY = e.originalEvent.deltaY;
10469 } else if (e.originalEvent.wheelDelta) {
10470 deltaY = -e.originalEvent.wheelDelta;
10471 }
10472 ViewerZoomIn( deltaY <= 0 ? true : false );
10473 }
10474 }
10475 });
10476
10477 // mouse move -> unhide lightbox toolbars
10478 jQuery(window).bind('mousemove', function(e){
10479 if( G.VOM.viewerDisplayed ) {
10480 if( G.VOM.toolbarsDisplayed == false ) {
10481 G.VOM.singletapTime = new Date().getTime(); // to avoid conflict with SINGLETAP event
10482 debounce( ViewerToolsUnHide, 100, false )();
10483 }
10484 }
10485 });
10486
10487 // fullscreen mode on/off --> internal lightbox
10488 if( ngscreenfull.enabled ) {
10489 // ngscreenfull.onchange(() => {
10490 ngscreenfull.onchange( function() {
10491 if( G.VOM.viewerDisplayed ) {
10492 if( ngscreenfull.isFullscreen ) {
10493 G.VOM.viewerIsFullscreen=true;
10494 G.VOM.$viewer.find('.fullscreenButton').html(G.O.icons.viewerFullscreenOff);
10495 }
10496 else {
10497 G.VOM.viewerIsFullscreen=false;
10498 G.VOM.$viewer.find('.fullscreenButton').html(G.O.icons.viewerFullscreenOn);
10499 }
10500 }
10501 });
10502 }
10503
10504 }
10505
10506
10507 //----- Manage browser location hash (deep linking and browser back/forward)
10508 function ProcessLocationHash() {
10509
10510 // standard use case -> location hash processing
10511 if( !G.O.locationHash ) { return false; }
10512
10513 var curGal = '#nanogallery/' + G.baseEltID + '/',
10514 newLocationHash = location.hash;
10515 if( G.O.debugMode ) {
10516 console.log('------------------------ PROCESS LOCATION HASH');
10517 console.log('newLocationHash1: ' +newLocationHash);
10518 console.log('G.locationHashLastUsed: ' +G.locationHashLastUsed);
10519 }
10520
10521 if( newLocationHash == '' ) {
10522 // if( G.GOM.lastDisplayedIdx != -1 ) {
10523 if( G.locationHashLastUsed !== '' ) {
10524 // back button and no hash --> display first album
10525 if( G.O.debugMode ) { console.log('display root album' ); }
10526 G.locationHashLastUsed = '';
10527 if( G.O.debugMode ) { console.log('new3 G.locationHashLastUsed: ' + G.locationHashLastUsed); }
10528 DisplayAlbum('', '0');
10529 return true;
10530 }
10531 }
10532
10533 if( newLocationHash == G.locationHashLastUsed ) { return; }
10534
10535 if( newLocationHash.indexOf(curGal) == 0 ) {
10536 // item IDs detected
10537 var IDs=parseIDs( newLocationHash.substring(curGal.length) );
10538 if( IDs.imageID != '0' ) {
10539 if( G.O.debugMode ) { console.log('display image: ' + IDs.albumID +'-'+ IDs.imageID ); }
10540 DisplayPhoto( IDs.imageID, IDs.albumID );
10541 return true;
10542 }
10543 else {
10544 if( G.O.debugMode ) { console.log('display album: ' + IDs.albumID ); }
10545 DisplayAlbum( '-1', IDs.albumID );
10546 return true;
10547 }
10548 }
10549
10550 return false;
10551 }
10552
10553 //---- Set a new browser location hash
10554 function SetLocationHash(albumID, imageID ) {
10555 if( !G.O.locationHash || G.O.lightboxStandalone ) { return false; }
10556
10557 if( G.O.debugMode ) {
10558 console.log('------------------------ SET LOCATION HASH');
10559 }
10560
10561 if( imageID == '' && (albumID == '-1' || albumID == '0' || G.O.album == albumID ) ) {
10562 // root album level --> do not set top.location.hash if not already set
10563 if( location.hash != '' ) {
10564 // try to clear the hash if set
10565 if ("pushState" in history) {
10566 history.pushState("", document.title, window.location.pathname + window.location.search);
10567 }
10568 else {
10569 location.hash='';
10570 }
10571 }
10572 G.locationHashLastUsed='';
10573 if( G.O.debugMode ) { console.log('new2 G.locationHashLastUsed: '+G.locationHashLastUsed); }
10574 return;
10575 }
10576
10577 var newLocationHash='#'+'nanogallery/'+G.baseEltID+'/'+ albumID;
10578 if( imageID != '' ) {
10579 newLocationHash+='/'+imageID;
10580 }
10581
10582 var lH=location.hash;
10583 if( G.O.debugMode ) {
10584 console.log('newLocationHash2: '+newLocationHash);
10585 console.log('location.hash: '+lH);
10586 }
10587
10588 G.locationHashLastUsed=newLocationHash;
10589 if( G.O.debugMode ) { console.log('new G.locationHashLastUsed: '+G.locationHashLastUsed); }
10590
10591 if( lH == '' || lH != newLocationHash ) {
10592 // G.locationHashLastUsed='#'+newLocationHash;
10593 try {
10594 top.location.hash=newLocationHash;
10595 }
10596 catch(e) {
10597 // location hash is not supported by current browser --> disable the option
10598 G.O.locationHash=false;
10599 }
10600 }
10601 }
10602
10603
10604 // WINDOW RESIZE EVENT
10605 function ResizeWindowEvent() {
10606 CacheViewport();
10607
10608 var l = G.GOM.curNavLevel;
10609 var w = G.GOM.curWidth;
10610
10611 if( G.VOM.viewerDisplayed ) {
10612 // lightbox
10613 ResizeLightbox();
10614 G.VOM.gallery.Resize();
10615 }
10616 else {
10617 // gallery
10618 if( G.galleryResizeEventEnabled ) {
10619 var nw = RetrieveCurWidth();
10620
10621 if( G.GOM.albumIdx != -1 ) {
10622
10623 // check if the gallery needs to be rendered again because the width changed
10624
10625 var s = G.tn.settings;
10626 if( G.layout.engine == "MOSAIC") {
10627 // Mosaic layout
10628 if( JSON.stringify(s.mosaic[l][w]) !== JSON.stringify(s.mosaic[l][nw]) ) {
10629 // mosaic definition changed
10630 G.GOM.curWidth = nw;
10631 G.GOM.pagination.currentPage = 0;
10632 GalleryRender( G.GOM.albumIdx );
10633 }
10634 }
10635 else {
10636 // other layouts
10637 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] ) {
10638 // thumbnail size / gutter size changed --> render the gallery with the new values
10639 G.GOM.curWidth = nw;
10640 //G.layout.SetEngine();
10641 G.GOM.pagination.currentPage = 0;
10642 GalleryRender( G.GOM.albumIdx );
10643 }
10644 }
10645 return;
10646 }
10647 else {
10648 GalleryResize();
10649 }
10650 }
10651 }
10652 }
10653
10654
10655
10656 // SCROLL EVENT -> on WINDOW or SCROLLABLE PARENT CONTAINER
10657 function OnScrollEvent() {
10658 if( !G.VOM.viewerDisplayed ) {
10659 GalleryResizeOnScrollEvent();
10660 }
10661 }
10662
10663 // 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
10664 function GalleryResizeOnScrollEvent() {
10665 if( G.galleryResizeEventEnabled == false) {
10666 window.setTimeout(GalleryResizeOnScrollEvent, 10); // check again in 10ms
10667 } else {
10668 GalleryResize();
10669 }
10670 }
10671
10672
10673
10674 // I18N : define text translations
10675 function i18n() {
10676
10677 // browser language
10678 G.i18nLang = (navigator.language || navigator.userLanguage).toUpperCase();
10679 if( G.i18nLang === 'UNDEFINED') { G.i18nLang=''; }
10680
10681 var llang=-('_'+G.i18nLang).length;
10682
10683 if( toType(G.O.i18n) == 'object' ){
10684
10685 for( var key in G.O.i18n ) {
10686 //var value = G.O.i18n[key];
10687 var s=key.substr(llang);
10688 if( s == ('_'+G.i18nLang) ) {
10689 G.i18nTranslations[key.substr(0,key.length-s.length)]=G.O.i18n[key];
10690 }
10691 else {
10692 G.i18nTranslations[key]=G.O.i18n[key];
10693 }
10694 }
10695 }
10696 }
10697
10698 function GetI18nItem( item, property ) {
10699 var s='';
10700 if( G.i18nLang != '' ) {
10701 if( item[property+'_'+G.i18nLang] !== undefined && item[property+'_'+G.i18nLang].length>0 ) {
10702 s=item[property+'_'+G.i18nLang];
10703 return s;
10704 }
10705 }
10706 s=item[property];
10707 return s;
10708 }
10709
10710
10711 function RetrieveCurWidth() {
10712 var vpW = G.GOM.cache.viewport.w;
10713
10714 if( G.O.breakpointSizeSM > 0 && vpW < G.O.breakpointSizeSM) { return 'xs'; }
10715 if( G.O.breakpointSizeME > 0 && vpW < G.O.breakpointSizeME) { return 'sm'; }
10716 if( G.O.breakpointSizeLA > 0 && vpW < G.O.breakpointSizeLA) { return 'me'; }
10717 if( G.O.breakpointSizeXL > 0 && vpW < G.O.breakpointSizeXL) { return 'la'; }
10718
10719 return 'xl';
10720 }
10721
10722
10723 /** @function browserNotification */
10724 function browserNotification() {
10725 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>';
10726 m += '&nbsp;&nbsp;&nbsp; <a href="http://www.google.com/chrome/?hl=en-US)">Chrome</a><br>';
10727 m += '&nbsp;&nbsp;&nbsp; <a href="http://www.mozilla.com/firefox/)">Firefox</a><br>';
10728 m += '&nbsp;&nbsp;&nbsp; <a href="http://www.microsoft.com/windows/internet-explorer/default.aspx">Internet Explorer</a><br>';
10729 m += '&nbsp;&nbsp;&nbsp; <a href="http://www.apple.com/safari/download/">Safari</a>';
10730 NanoAlert(G, m, false);
10731 }
10732
10733 // 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
10734 function FirstSupportedPropertyName(prefixedPropertyNames) {
10735 var tempDiv = document.createElement("div");
10736 for (var i = 0; i < prefixedPropertyNames.length; ++i) {
10737 if (typeof tempDiv.style[prefixedPropertyNames[i]] != 'undefined')
10738 return prefixedPropertyNames[i];
10739 }
10740 return null;
10741 }
10742
10743
10744
10745
10746 }
10747
10748
10749
10750//##########################################################################################################################
10751//## imagesLoaded ##########################################################################################################
10752//##########################################################################################################################
10753
10754// external module EMBEDED in nanogallery
10755// NGY BUILD:
10756// replace "imagesLoaded" with "ngimagesLoaded"
10757// replace "ImagesLoaded" with "ngImagesLoaded"
10758// replace "EvEmitter" with "ngEvEmitter"
10759// replace "var $ = window.jQuery" with "var $ = jQuery;"
10760// 2x (global.ngEvEmitter and window.ngimagesLoaded = f...)ignore package manager and set browser global
10761
10762
10763
10764/*!
10765 * imagesLoaded PACKAGED v4.1.1
10766 * JavaScript is all like "You images are done yet or what?"
10767 * MIT License
10768 */
10769
10770/**
10771 * EvEmitter v1.0.3
10772 * Lil' event emitter
10773 * MIT License
10774 */
10775
10776
10777/* jshint unused: true, undef: true, strict: true */
10778
10779( function( global, factory ) {
10780 // universal module definition
10781 /* jshint strict: false */ /* globals define, module, window */
10782// if ( typeof define == 'function' && define.amd ) {
10783 // AMD - RequireJS
10784// define( 'ev-emitter/ev-emitter',factory );
10785// } else if ( typeof module == 'object' && module.exports ) {
10786 // CommonJS - Browserify, Webpack
10787// module.exports = factory();
10788// } else {
10789 // Browser globals
10790 global.ngEvEmitter = factory();
10791// }
10792
10793}( typeof window != 'undefined' ? window : this, function() {
10794
10795
10796
10797function ngEvEmitter() {}
10798
10799var proto = ngEvEmitter.prototype;
10800
10801proto.on = function( eventName, listener ) {
10802 if ( !eventName || !listener ) {
10803 return;
10804 }
10805 // set events hash
10806 var events = this._events = this._events || {};
10807 // set listeners array
10808 var listeners = events[ eventName ] = events[ eventName ] || [];
10809 // only add once
10810 if ( listeners.indexOf( listener ) == -1 ) {
10811 listeners.push( listener );
10812 }
10813
10814 return this;
10815};
10816
10817proto.once = function( eventName, listener ) {
10818 if ( !eventName || !listener ) {
10819 return;
10820 }
10821 // add event
10822 this.on( eventName, listener );
10823 // set once flag
10824 // set onceEvents hash
10825 var onceEvents = this._onceEvents = this._onceEvents || {};
10826 // set onceListeners object
10827 var onceListeners = onceEvents[ eventName ] = onceEvents[ eventName ] || {};
10828 // set flag
10829 onceListeners[ listener ] = true;
10830
10831 return this;
10832};
10833
10834proto.off = function( eventName, listener ) {
10835 var listeners = this._events && this._events[ eventName ];
10836 if ( !listeners || !listeners.length ) {
10837 return;
10838 }
10839 var index = listeners.indexOf( listener );
10840 if ( index != -1 ) {
10841 listeners.splice( index, 1 );
10842 }
10843
10844 return this;
10845};
10846
10847proto.emitEvent = function( eventName, args ) {
10848 var listeners = this._events && this._events[ eventName ];
10849 if ( !listeners || !listeners.length ) {
10850 return;
10851 }
10852 var i = 0;
10853 var listener = listeners[i];
10854 args = args || [];
10855 // once stuff
10856 var onceListeners = this._onceEvents && this._onceEvents[ eventName ];
10857
10858 while ( listener ) {
10859 var isOnce = onceListeners && onceListeners[ listener ];
10860 if ( isOnce ) {
10861 // remove listener
10862 // remove before trigger to prevent recursion
10863 this.off( eventName, listener );
10864 // unset once flag
10865 delete onceListeners[ listener ];
10866 }
10867 // trigger listener
10868 listener.apply( this, args );
10869 // get next listener
10870 i += isOnce ? 0 : 1;
10871 listener = listeners[i];
10872 }
10873
10874 return this;
10875};
10876
10877return ngEvEmitter;
10878
10879}));
10880
10881/*!
10882 * ngimagesLoaded v4.1.1
10883 * JavaScript is all like "You images are done yet or what?"
10884 * MIT License
10885 */
10886
10887( function( window, factory ) { 'use strict';
10888 // universal module definition
10889
10890 /*global define: false, module: false, require: false */
10891
10892// if ( typeof define == 'function' && define.amd ) {
10893 // AMD
10894// define( [
10895// 'ev-emitter/ev-emitter'
10896// ], function( ngEvEmitter ) {
10897// return factory( window, ngEvEmitter );
10898// });
10899// } else if ( typeof module == 'object' && module.exports ) {
10900 // CommonJS
10901// module.exports = factory(
10902// window,
10903// require('ev-emitter')
10904// );
10905// } else {
10906 // browser global
10907 window.ngimagesLoaded = factory(
10908 window,
10909 window.ngEvEmitter
10910 );
10911 //}
10912
10913})( window,
10914
10915// -------------------------- factory -------------------------- //
10916
10917function factory( window, ngEvEmitter ) {
10918
10919
10920
10921// var $ = window.jQuery;
10922var $ = jQuery;
10923var console = window.console;
10924
10925// -------------------------- helpers -------------------------- //
10926
10927// extend objects
10928function extend( a, b ) {
10929 for ( var prop in b ) {
10930 a[ prop ] = b[ prop ];
10931 }
10932 return a;
10933}
10934
10935// turn element or nodeList into an array
10936function makeArray( obj ) {
10937 var ary = [];
10938 if ( Array.isArray( obj ) ) {
10939 // use object if already an array
10940 ary = obj;
10941 } else if ( typeof obj.length == 'number' ) {
10942 // convert nodeList to array
10943 for ( var i=0; i < obj.length; i++ ) {
10944 ary.push( obj[i] );
10945 }
10946 } else {
10947 // array of single index
10948 ary.push( obj );
10949 }
10950 return ary;
10951}
10952
10953// -------------------------- ngimagesLoaded -------------------------- //
10954
10955/**
10956 * @param {Array, Element, NodeList, String} elem
10957 * @param {Object or Function} options - if function, use as callback
10958 * @param {Function} onAlways - callback function
10959 */
10960function ngImagesLoaded( elem, options, onAlways ) {
10961 // coerce ngImagesLoaded() without new, to be new ngImagesLoaded()
10962 if ( !( this instanceof ngImagesLoaded ) ) {
10963 return new ngImagesLoaded( elem, options, onAlways );
10964 }
10965 // use elem as selector string
10966 if ( typeof elem == 'string' ) {
10967 elem = document.querySelectorAll( elem );
10968 }
10969
10970 this.elements = makeArray( elem );
10971 this.options = extend( {}, this.options );
10972
10973 if ( typeof options == 'function' ) {
10974 onAlways = options;
10975 } else {
10976 extend( this.options, options );
10977 }
10978
10979 if ( onAlways ) {
10980 this.on( 'always', onAlways );
10981 }
10982
10983 this.getImages();
10984
10985 if ( $ ) {
10986 // add jQuery Deferred object
10987 this.jqDeferred = new $.Deferred();
10988 }
10989
10990 // HACK check async to allow time to bind listeners
10991 setTimeout( function() {
10992 this.check();
10993 }.bind( this ));
10994}
10995
10996ngImagesLoaded.prototype = Object.create( ngEvEmitter.prototype );
10997
10998ngImagesLoaded.prototype.options = {};
10999
11000ngImagesLoaded.prototype.getImages = function() {
11001 this.images = [];
11002
11003 // filter & find items if we have an item selector
11004 this.elements.forEach( this.addElementImages, this );
11005};
11006
11007/**
11008 * @param {Node} element
11009 */
11010ngImagesLoaded.prototype.addElementImages = function( elem ) {
11011 // filter siblings
11012 if ( elem.nodeName == 'IMG' ) {
11013 this.addImage( elem );
11014 }
11015 // get background image on element
11016 if ( this.options.background === true ) {
11017 this.addElementBackgroundImages( elem );
11018 }
11019
11020 // find children
11021 // no non-element nodes, #143
11022 var nodeType = elem.nodeType;
11023 if ( !nodeType || !elementNodeTypes[ nodeType ] ) {
11024 return;
11025 }
11026 var childImgs = elem.querySelectorAll('img');
11027 // concat childElems to filterFound array
11028 for ( var i=0; i < childImgs.length; i++ ) {
11029 var img = childImgs[i];
11030 this.addImage( img );
11031 }
11032
11033 // get child background images
11034 if ( typeof this.options.background == 'string' ) {
11035 var children = elem.querySelectorAll( this.options.background );
11036 for ( i=0; i < children.length; i++ ) {
11037 var child = children[i];
11038 this.addElementBackgroundImages( child );
11039 }
11040 }
11041};
11042
11043var elementNodeTypes = {
11044 1: true,
11045 9: true,
11046 11: true
11047};
11048
11049ngImagesLoaded.prototype.addElementBackgroundImages = function( elem ) {
11050 var style = getComputedStyle( elem );
11051 if ( !style ) {
11052 // Firefox returns null if in a hidden iframe https://bugzil.la/548397
11053 return;
11054 }
11055 // get url inside url("...")
11056 var reURL = /url\((['"])?(.*?)\1\)/gi;
11057 var matches = reURL.exec( style.backgroundImage );
11058 while ( matches !== null ) {
11059 var url = matches && matches[2];
11060 if ( url ) {
11061 this.addBackground( url, elem );
11062 }
11063 matches = reURL.exec( style.backgroundImage );
11064 }
11065};
11066
11067/**
11068 * @param {Image} img
11069 */
11070ngImagesLoaded.prototype.addImage = function( img ) {
11071 var loadingImage = new LoadingImage( img );
11072 this.images.push( loadingImage );
11073};
11074
11075ngImagesLoaded.prototype.addBackground = function( url, elem ) {
11076 var background = new Background( url, elem );
11077 this.images.push( background );
11078};
11079
11080ngImagesLoaded.prototype.check = function() {
11081 var _this = this;
11082 this.progressedCount = 0;
11083 this.hasAnyBroken = false;
11084 // complete if no images
11085 if ( !this.images.length ) {
11086 this.complete();
11087 return;
11088 }
11089
11090 function onProgress( image, elem, message ) {
11091 // HACK - Chrome triggers event before object properties have changed. #83
11092 setTimeout( function() {
11093 _this.progress( image, elem, message );
11094 });
11095 }
11096
11097 this.images.forEach( function( loadingImage ) {
11098 loadingImage.once( 'progress', onProgress );
11099 loadingImage.check();
11100 });
11101};
11102
11103ngImagesLoaded.prototype.progress = function( image, elem, message ) {
11104 this.progressedCount++;
11105 this.hasAnyBroken = this.hasAnyBroken || !image.isLoaded;
11106 // progress event
11107 this.emitEvent( 'progress', [ this, image, elem ] );
11108 if ( this.jqDeferred && this.jqDeferred.notify ) {
11109 this.jqDeferred.notify( this, image );
11110 }
11111 // check if completed
11112 if ( this.progressedCount == this.images.length ) {
11113 this.complete();
11114 }
11115
11116 if ( this.options.debug && console ) {
11117 console.log( 'progress: ' + message, image, elem );
11118 }
11119};
11120
11121ngImagesLoaded.prototype.complete = function() {
11122 var eventName = this.hasAnyBroken ? 'fail' : 'done';
11123 this.isComplete = true;
11124 this.emitEvent( eventName, [ this ] );
11125 this.emitEvent( 'always', [ this ] );
11126 if ( this.jqDeferred ) {
11127 var jqMethod = this.hasAnyBroken ? 'reject' : 'resolve';
11128 this.jqDeferred[ jqMethod ]( this );
11129 }
11130};
11131
11132// -------------------------- -------------------------- //
11133
11134function LoadingImage( img ) {
11135 this.img = img;
11136}
11137
11138LoadingImage.prototype = Object.create( ngEvEmitter.prototype );
11139
11140LoadingImage.prototype.check = function() {
11141 // If complete is true and browser supports natural sizes,
11142 // try to check for image status manually.
11143 var isComplete = this.getIsImageComplete();
11144 if ( isComplete ) {
11145 // report based on naturalWidth
11146 this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' );
11147 return;
11148 }
11149
11150 // If none of the checks above matched, simulate loading on detached element.
11151 this.proxyImage = new Image();
11152 this.proxyImage.addEventListener( 'load', this );
11153 this.proxyImage.addEventListener( 'error', this );
11154 // bind to image as well for Firefox. #191
11155 this.img.addEventListener( 'load', this );
11156 this.img.addEventListener( 'error', this );
11157 this.proxyImage.src = this.img.src;
11158};
11159
11160LoadingImage.prototype.getIsImageComplete = function() {
11161 return this.img.complete && this.img.naturalWidth !== undefined;
11162};
11163
11164LoadingImage.prototype.confirm = function( isLoaded, message ) {
11165 this.isLoaded = isLoaded;
11166 this.emitEvent( 'progress', [ this, this.img, message ] );
11167};
11168
11169// ----- events ----- //
11170
11171// trigger specified handler for event type
11172LoadingImage.prototype.handleEvent = function( event ) {
11173 var method = 'on' + event.type;
11174 if ( this[ method ] ) {
11175 this[ method ]( event );
11176 }
11177};
11178
11179LoadingImage.prototype.onload = function() {
11180 this.confirm( true, 'onload' );
11181 this.unbindEvents();
11182};
11183
11184LoadingImage.prototype.onerror = function() {
11185 this.confirm( false, 'onerror' );
11186 this.unbindEvents();
11187};
11188
11189LoadingImage.prototype.unbindEvents = function() {
11190 this.proxyImage.removeEventListener( 'load', this );
11191 this.proxyImage.removeEventListener( 'error', this );
11192 this.img.removeEventListener( 'load', this );
11193 this.img.removeEventListener( 'error', this );
11194};
11195
11196// -------------------------- Background -------------------------- //
11197
11198function Background( url, element ) {
11199 this.url = url;
11200 this.element = element;
11201 this.img = new Image();
11202}
11203
11204// inherit LoadingImage prototype
11205Background.prototype = Object.create( LoadingImage.prototype );
11206
11207Background.prototype.check = function() {
11208 this.img.addEventListener( 'load', this );
11209 this.img.addEventListener( 'error', this );
11210 this.img.src = this.url;
11211 // check if image is already complete
11212 var isComplete = this.getIsImageComplete();
11213 if ( isComplete ) {
11214 this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' );
11215 this.unbindEvents();
11216 }
11217};
11218
11219Background.prototype.unbindEvents = function() {
11220 this.img.removeEventListener( 'load', this );
11221 this.img.removeEventListener( 'error', this );
11222};
11223
11224Background.prototype.confirm = function( isLoaded, message ) {
11225 this.isLoaded = isLoaded;
11226 this.emitEvent( 'progress', [ this, this.element, message ] );
11227};
11228
11229// -------------------------- jQuery -------------------------- //
11230
11231ngImagesLoaded.makeJQueryPlugin = function( jQuery ) {
11232 jQuery = jQuery || window.jQuery;
11233 if ( !jQuery ) {
11234 return;
11235 }
11236 // set local variable
11237 $ = jQuery;
11238 // $().ngimagesLoaded()
11239 $.fn.ngimagesLoaded = function( options, callback ) {
11240 var instance = new ngImagesLoaded( this, options, callback );
11241 return instance.jqDeferred.promise( $(this) );
11242 };
11243};
11244// try making plugin
11245ngImagesLoaded.makeJQueryPlugin();
11246
11247// -------------------------- -------------------------- //
11248
11249return ngImagesLoaded;
11250
11251});
11252
11253
11254
11255//##########################################################################################################################
11256//## screenfull.js #########################################################################################################
11257//##########################################################################################################################
11258
11259// screenfull.js
11260// v4.0.1
11261// by sindresorhus - https://github.com/sindresorhus
11262// from: https://github.com/sindresorhus/screenfull.js
11263
11264// external module embeded in nanogallery
11265// NGY BUILD:
11266// replace "screenfull" with "ngscreenfull"
11267//
11268
11269(function () {
11270 'use strict';
11271
11272 var document = typeof window !== 'undefined' && typeof window.document !== 'undefined' ? window.document : {};
11273 var isCommonjs = typeof module !== 'undefined' && module.exports;
11274 var keyboardAllowed = typeof Element !== 'undefined' && 'ALLOW_KEYBOARD_INPUT' in Element;
11275
11276 var fn = (function () {
11277 var val;
11278
11279 var fnMap = [
11280 [
11281 'requestFullscreen',
11282 'exitFullscreen',
11283 'fullscreenElement',
11284 'fullscreenEnabled',
11285 'fullscreenchange',
11286 'fullscreenerror'
11287 ],
11288 // New WebKit
11289 [
11290 'webkitRequestFullscreen',
11291 'webkitExitFullscreen',
11292 'webkitFullscreenElement',
11293 'webkitFullscreenEnabled',
11294 'webkitfullscreenchange',
11295 'webkitfullscreenerror'
11296
11297 ],
11298 // Old WebKit (Safari 5.1)
11299 [
11300 'webkitRequestFullScreen',
11301 'webkitCancelFullScreen',
11302 'webkitCurrentFullScreenElement',
11303 'webkitCancelFullScreen',
11304 'webkitfullscreenchange',
11305 'webkitfullscreenerror'
11306
11307 ],
11308 [
11309 'mozRequestFullScreen',
11310 'mozCancelFullScreen',
11311 'mozFullScreenElement',
11312 'mozFullScreenEnabled',
11313 'mozfullscreenchange',
11314 'mozfullscreenerror'
11315 ],
11316 [
11317 'msRequestFullscreen',
11318 'msExitFullscreen',
11319 'msFullscreenElement',
11320 'msFullscreenEnabled',
11321 'MSFullscreenChange',
11322 'MSFullscreenError'
11323 ]
11324 ];
11325
11326 var i = 0;
11327 var l = fnMap.length;
11328 var ret = {};
11329
11330 for (; i < l; i++) {
11331 val = fnMap[i];
11332 if (val && val[1] in document) {
11333 for (i = 0; i < val.length; i++) {
11334 ret[fnMap[0][i]] = val[i];
11335 }
11336 return ret;
11337 }
11338 }
11339
11340 return false;
11341 })();
11342
11343 var eventNameMap = {
11344 change: fn.fullscreenchange,
11345 error: fn.fullscreenerror
11346 };
11347
11348 var ngscreenfull = {
11349 request: function (elem) {
11350 return new Promise(function (resolve) {
11351 var request = fn.requestFullscreen;
11352
11353 var onFullScreenEntered = function () {
11354 this.off('change', onFullScreenEntered);
11355 resolve();
11356 }.bind(this);
11357
11358 elem = elem || document.documentElement;
11359
11360 // Work around Safari 5.1 bug: reports support for
11361 // keyboard in fullscreen even though it doesn't.
11362 // Browser sniffing, since the alternative with
11363 // setTimeout is even worse.
11364 if (/ Version\/5\.1(?:\.\d+)? Safari\//.test(navigator.userAgent)) {
11365 elem[request]();
11366 } else {
11367 elem[request](keyboardAllowed ? Element.ALLOW_KEYBOARD_INPUT : {});
11368 }
11369
11370 this.on('change', onFullScreenEntered);
11371 }.bind(this));
11372 },
11373 exit: function () {
11374 return new Promise(function (resolve) {
11375 if (!this.isFullscreen) {
11376 resolve();
11377 return;
11378 }
11379
11380 var onFullScreenExit = function () {
11381 this.off('change', onFullScreenExit);
11382 resolve();
11383 }.bind(this);
11384
11385 document[fn.exitFullscreen]();
11386
11387 this.on('change', onFullScreenExit);
11388 }.bind(this));
11389 },
11390 toggle: function (elem) {
11391 return this.isFullscreen ? this.exit() : this.request(elem);
11392 },
11393 onchange: function (callback) {
11394 this.on('change', callback);
11395 },
11396 onerror: function (callback) {
11397 this.on('error', callback);
11398 },
11399 on: function (event, callback) {
11400 var eventName = eventNameMap[event];
11401 if (eventName) {
11402 document.addEventListener(eventName, callback, false);
11403 }
11404 },
11405 off: function (event, callback) {
11406 var eventName = eventNameMap[event];
11407 if (eventName) {
11408 document.removeEventListener(eventName, callback, false);
11409 }
11410 },
11411 raw: fn
11412 };
11413
11414 if (!fn) {
11415 if (isCommonjs) {
11416 module.exports = false;
11417 } else {
11418 window.ngscreenfull = false;
11419 }
11420
11421 return;
11422 }
11423
11424 Object.defineProperties(ngscreenfull, {
11425 isFullscreen: {
11426 get: function () {
11427 return Boolean(document[fn.fullscreenElement]);
11428 }
11429 },
11430 element: {
11431 enumerable: true,
11432 get: function () {
11433 return document[fn.fullscreenElement];
11434 }
11435 },
11436 enabled: {
11437 enumerable: true,
11438 get: function () {
11439 // Coerce to boolean in case of old WebKit
11440 return Boolean(document[fn.fullscreenEnabled]);
11441 }
11442 }
11443 });
11444
11445 if (isCommonjs) {
11446 module.exports = ngscreenfull;
11447 } else {
11448 window.ngscreenfull = ngscreenfull;
11449 }
11450})();
11451
11452
11453
11454//##########################################################################################################################
11455//## Shifty ################################################################################################################
11456//##########################################################################################################################
11457
11458 /*!
11459 * Shifty
11460 * By Jeremy Kahn - jeremyckahn@gmail.com
11461 */
11462
11463// external module EMBEDED in nanogallery
11464// NGY BUILD:
11465//
11466// replace "Tweenable" with "NGTweenable"
11467// replace "define.amd" with "define.amdDISABLED"
11468/* shifty - v1.5.3 - 2016-11-29 - http://jeremyckahn.github.io/shifty */
11469;(function () {
11470 var root = this || Function('return this')();
11471
11472/**
11473 * Shifty Core
11474 * By Jeremy Kahn - jeremyckahn@gmail.com
11475 */
11476
11477var NGTweenable = (function () {
11478
11479 'use strict';
11480
11481 // Aliases that get defined later in this function
11482 var formula;
11483
11484 // CONSTANTS
11485 var DEFAULT_SCHEDULE_FUNCTION;
11486 var DEFAULT_EASING = 'linear';
11487 var DEFAULT_DURATION = 500;
11488 var UPDATE_TIME = 1000 / 60;
11489
11490 var _now = Date.now
11491 ? Date.now
11492 : function () {return +new Date();};
11493
11494 var now = typeof SHIFTY_DEBUG_NOW !== 'undefined' ? SHIFTY_DEBUG_NOW : _now;
11495
11496 if (typeof window !== 'undefined') {
11497 // requestAnimationFrame() shim by Paul Irish (modified for Shifty)
11498 // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
11499 DEFAULT_SCHEDULE_FUNCTION = window.requestAnimationFrame
11500 || window.webkitRequestAnimationFrame
11501 || window.oRequestAnimationFrame
11502 || window.msRequestAnimationFrame
11503 || (window.mozCancelRequestAnimationFrame
11504 && window.mozRequestAnimationFrame)
11505 || setTimeout;
11506 } else {
11507 DEFAULT_SCHEDULE_FUNCTION = setTimeout;
11508 }
11509
11510 function noop () {
11511 // NOOP!
11512 }
11513
11514 /**
11515 * Handy shortcut for doing a for-in loop. This is not a "normal" each
11516 * function, it is optimized for Shifty. The iterator function only receives
11517 * the property name, not the value.
11518 * @param {Object} obj
11519 * @param {Function(string)} fn
11520 * @private
11521 */
11522 function each (obj, fn) {
11523 var key;
11524 for (key in obj) {
11525 if (Object.hasOwnProperty.call(obj, key)) {
11526 fn(key);
11527 }
11528 }
11529 }
11530
11531 /**
11532 * Perform a shallow copy of Object properties.
11533 * @param {Object} targetObject The object to copy into
11534 * @param {Object} srcObject The object to copy from
11535 * @return {Object} A reference to the augmented `targetObj` Object
11536 * @private
11537 */
11538 function shallowCopy (targetObj, srcObj) {
11539 each(srcObj, function (prop) {
11540 targetObj[prop] = srcObj[prop];
11541 });
11542
11543 return targetObj;
11544 }
11545
11546 /**
11547 * Copies each property from src onto target, but only if the property to
11548 * copy to target is undefined.
11549 * @param {Object} target Missing properties in this Object are filled in
11550 * @param {Object} src
11551 * @private
11552 */
11553 function defaults (target, src) {
11554 each(src, function (prop) {
11555 if (typeof target[prop] === 'undefined') {
11556 target[prop] = src[prop];
11557 }
11558 });
11559 }
11560
11561 /**
11562 * Calculates the interpolated tween values of an Object for a given
11563 * timestamp.
11564 * @param {Number} forPosition The position to compute the state for.
11565 * @param {Object} currentState Current state properties.
11566 * @param {Object} originalState: The original state properties the Object is
11567 * tweening from.
11568 * @param {Object} targetState: The destination state properties the Object
11569 * is tweening to.
11570 * @param {number} duration: The length of the tween in milliseconds.
11571 * @param {number} timestamp: The UNIX epoch time at which the tween began.
11572 * @param {Object} easing: This Object's keys must correspond to the keys in
11573 * targetState.
11574 * @private
11575 */
11576 function tweenProps (forPosition, currentState, originalState, targetState,
11577 duration, timestamp, easing) {
11578 var normalizedPosition =
11579 forPosition < timestamp ? 0 : (forPosition - timestamp) / duration;
11580
11581
11582 var prop;
11583 var easingObjectProp;
11584 var easingFn;
11585 for (prop in currentState) {
11586 if (currentState.hasOwnProperty(prop)) {
11587 easingObjectProp = easing[prop];
11588 easingFn = typeof easingObjectProp === 'function'
11589 ? easingObjectProp
11590 : formula[easingObjectProp];
11591
11592 currentState[prop] = tweenProp(
11593 originalState[prop],
11594 targetState[prop],
11595 easingFn,
11596 normalizedPosition
11597 );
11598 }
11599 }
11600
11601 return currentState;
11602 }
11603
11604 /**
11605 * Tweens a single property.
11606 * @param {number} start The value that the tween started from.
11607 * @param {number} end The value that the tween should end at.
11608 * @param {Function} easingFunc The easing curve to apply to the tween.
11609 * @param {number} position The normalized position (between 0.0 and 1.0) to
11610 * calculate the midpoint of 'start' and 'end' against.
11611 * @return {number} The tweened value.
11612 * @private
11613 */
11614 function tweenProp (start, end, easingFunc, position) {
11615 return start + (end - start) * easingFunc(position);
11616 }
11617
11618 /**
11619 * Applies a filter to NGTweenable instance.
11620 * @param {NGTweenable} tweenable The `NGTweenable` instance to call the filter
11621 * upon.
11622 * @param {String} filterName The name of the filter to apply.
11623 * @private
11624 */
11625 function applyFilter (tweenable, filterName) {
11626 var filters = NGTweenable.prototype.filter;
11627 var args = tweenable._filterArgs;
11628
11629 each(filters, function (name) {
11630 if (typeof filters[name][filterName] !== 'undefined') {
11631 filters[name][filterName].apply(tweenable, args);
11632 }
11633 });
11634 }
11635
11636 var timeoutHandler_endTime;
11637 var timeoutHandler_currentTime;
11638 var timeoutHandler_isEnded;
11639 var timeoutHandler_offset;
11640 /**
11641 * Handles the update logic for one step of a tween.
11642 * @param {NGTweenable} tweenable
11643 * @param {number} timestamp
11644 * @param {number} delay
11645 * @param {number} duration
11646 * @param {Object} currentState
11647 * @param {Object} originalState
11648 * @param {Object} targetState
11649 * @param {Object} easing
11650 * @param {Function(Object, *, number)} step
11651 * @param {Function(Function,number)}} schedule
11652 * @param {number=} opt_currentTimeOverride Needed for accurate timestamp in
11653 * NGTweenable#seek.
11654 * @private
11655 */
11656 function timeoutHandler (tweenable, timestamp, delay, duration, currentState,
11657 originalState, targetState, easing, step, schedule,
11658 opt_currentTimeOverride) {
11659
11660 timeoutHandler_endTime = timestamp + delay + duration;
11661
11662 timeoutHandler_currentTime =
11663 Math.min(opt_currentTimeOverride || now(), timeoutHandler_endTime);
11664
11665 timeoutHandler_isEnded =
11666 timeoutHandler_currentTime >= timeoutHandler_endTime;
11667
11668 timeoutHandler_offset = duration - (
11669 timeoutHandler_endTime - timeoutHandler_currentTime);
11670
11671 if (tweenable.isPlaying()) {
11672 if (timeoutHandler_isEnded) {
11673 step(targetState, tweenable._attachment, timeoutHandler_offset);
11674 tweenable.stop(true);
11675 } else {
11676 tweenable._scheduleId =
11677 schedule(tweenable._timeoutHandler, UPDATE_TIME);
11678
11679 applyFilter(tweenable, 'beforeTween');
11680
11681 // If the animation has not yet reached the start point (e.g., there was
11682 // delay that has not yet completed), just interpolate the starting
11683 // position of the tween.
11684 if (timeoutHandler_currentTime < (timestamp + delay)) {
11685 tweenProps(1, currentState, originalState, targetState, 1, 1, easing);
11686 } else {
11687 tweenProps(timeoutHandler_currentTime, currentState, originalState,
11688 targetState, duration, timestamp + delay, easing);
11689 }
11690
11691 applyFilter(tweenable, 'afterTween');
11692
11693 step(currentState, tweenable._attachment, timeoutHandler_offset);
11694 }
11695 }
11696 }
11697
11698
11699 /**
11700 * Creates a usable easing Object from a string, a function or another easing
11701 * Object. If `easing` is an Object, then this function clones it and fills
11702 * in the missing properties with `"linear"`.
11703 * @param {Object.<string|Function>} fromTweenParams
11704 * @param {Object|string|Function} easing
11705 * @return {Object.<string|Function>}
11706 * @private
11707 */
11708 function composeEasingObject (fromTweenParams, easing) {
11709 var composedEasing = {};
11710 var typeofEasing = typeof easing;
11711
11712 if (typeofEasing === 'string' || typeofEasing === 'function') {
11713 each(fromTweenParams, function (prop) {
11714 composedEasing[prop] = easing;
11715 });
11716 } else {
11717 each(fromTweenParams, function (prop) {
11718 if (!composedEasing[prop]) {
11719 composedEasing[prop] = easing[prop] || DEFAULT_EASING;
11720 }
11721 });
11722 }
11723
11724 return composedEasing;
11725 }
11726
11727 /**
11728 * NGTweenable constructor.
11729 * @class NGTweenable
11730 * @param {Object=} opt_initialState The values that the initial tween should
11731 * start at if a `from` object is not provided to `{{#crossLink
11732 * "NGTweenable/tween:method"}}{{/crossLink}}` or `{{#crossLink
11733 * "NGTweenable/setConfig:method"}}{{/crossLink}}`.
11734 * @param {Object=} opt_config Configuration object to be passed to
11735 * `{{#crossLink "NGTweenable/setConfig:method"}}{{/crossLink}}`.
11736 * @module NGTweenable
11737 * @constructor
11738 */
11739 function NGTweenable (opt_initialState, opt_config) {
11740 this._currentState = opt_initialState || {};
11741 this._configured = false;
11742 this._scheduleFunction = DEFAULT_SCHEDULE_FUNCTION;
11743
11744 // To prevent unnecessary calls to setConfig do not set default
11745 // configuration here. Only set default configuration immediately before
11746 // tweening if none has been set.
11747 if (typeof opt_config !== 'undefined') {
11748 this.setConfig(opt_config);
11749 }
11750 }
11751
11752 /**
11753 * Configure and start a tween.
11754 * @method tween
11755 * @param {Object=} opt_config Configuration object to be passed to
11756 * `{{#crossLink "NGTweenable/setConfig:method"}}{{/crossLink}}`.
11757 * @chainable
11758 */
11759 NGTweenable.prototype.tween = function (opt_config) {
11760 if (this._isTweening) {
11761 return this;
11762 }
11763
11764 // Only set default config if no configuration has been set previously and
11765 // none is provided now.
11766 if (opt_config !== undefined || !this._configured) {
11767 this.setConfig(opt_config);
11768 }
11769
11770 this._timestamp = now();
11771 this._start(this.get(), this._attachment);
11772 return this.resume();
11773 };
11774
11775 /**
11776 * Configure a tween that will start at some point in the future.
11777 *
11778 * @method setConfig
11779 * @param {Object} config The following values are valid:
11780 * - __from__ (_Object=_): Starting position. If omitted, `{{#crossLink
11781 * "NGTweenable/get:method"}}get(){{/crossLink}}` is used.
11782 * - __to__ (_Object=_): Ending position.
11783 * - __duration__ (_number=_): How many milliseconds to animate for.
11784 * - __delay__ (_delay=_): How many milliseconds to wait before starting the
11785 * tween.
11786 * - __start__ (_Function(Object, *)_): Function to execute when the tween
11787 * begins. Receives the state of the tween as the first parameter and
11788 * `attachment` as the second parameter.
11789 * - __step__ (_Function(Object, *, number)_): Function to execute on every
11790 * tick. Receives `{{#crossLink
11791 * "NGTweenable/get:method"}}get(){{/crossLink}}` as the first parameter,
11792 * `attachment` as the second parameter, and the time elapsed since the
11793 * start of the tween as the third. This function is not called on the
11794 * final step of the animation, but `finish` is.
11795 * - __finish__ (_Function(Object, *)_): Function to execute upon tween
11796 * completion. Receives the state of the tween as the first parameter and
11797 * `attachment` as the second parameter.
11798 * - __easing__ (_Object.<string|Function>|string|Function=_): Easing curve
11799 * name(s) or function(s) to use for the tween.
11800 * - __attachment__ (_*_): Cached value that is passed to the
11801 * `step`/`start`/`finish` methods.
11802 * @chainable
11803 */
11804 NGTweenable.prototype.setConfig = function (config) {
11805 config = config || {};
11806 this._configured = true;
11807
11808 // Attach something to this NGTweenable instance (e.g.: a DOM element, an
11809 // object, a string, etc.);
11810 this._attachment = config.attachment;
11811
11812 // Init the internal state
11813 this._pausedAtTime = null;
11814 this._scheduleId = null;
11815 this._delay = config.delay || 0;
11816 this._start = config.start || noop;
11817 this._step = config.step || noop;
11818 this._finish = config.finish || noop;
11819 this._duration = config.duration || DEFAULT_DURATION;
11820 this._currentState = shallowCopy({}, config.from || this.get());
11821 this._originalState = this.get();
11822 this._targetState = shallowCopy({}, config.to || this.get());
11823
11824 var self = this;
11825 this._timeoutHandler = function () {
11826 timeoutHandler(self,
11827 self._timestamp,
11828 self._delay,
11829 self._duration,
11830 self._currentState,
11831 self._originalState,
11832 self._targetState,
11833 self._easing,
11834 self._step,
11835 self._scheduleFunction
11836 );
11837 };
11838
11839 // Aliases used below
11840 var currentState = this._currentState;
11841 var targetState = this._targetState;
11842
11843 // Ensure that there is always something to tween to.
11844 defaults(targetState, currentState);
11845
11846 this._easing = composeEasingObject(
11847 currentState, config.easing || DEFAULT_EASING);
11848
11849 this._filterArgs =
11850 [currentState, this._originalState, targetState, this._easing];
11851
11852 applyFilter(this, 'tweenCreated');
11853 return this;
11854 };
11855
11856 /**
11857 * @method get
11858 * @return {Object} The current state.
11859 */
11860 NGTweenable.prototype.get = function () {
11861 return shallowCopy({}, this._currentState);
11862 };
11863
11864 /**
11865 * @method set
11866 * @param {Object} state The current state.
11867 */
11868 NGTweenable.prototype.set = function (state) {
11869 this._currentState = state;
11870 };
11871
11872 /**
11873 * Pause a tween. Paused tweens can be resumed from the point at which they
11874 * were paused. This is different from `{{#crossLink
11875 * "NGTweenable/stop:method"}}{{/crossLink}}`, as that method
11876 * causes a tween to start over when it is resumed.
11877 * @method pause
11878 * @chainable
11879 */
11880 NGTweenable.prototype.pause = function () {
11881 this._pausedAtTime = now();
11882 this._isPaused = true;
11883 return this;
11884 };
11885
11886 /**
11887 * Resume a paused tween.
11888 * @method resume
11889 * @chainable
11890 */
11891 NGTweenable.prototype.resume = function () {
11892 if (this._isPaused) {
11893 this._timestamp += now() - this._pausedAtTime;
11894 }
11895
11896 this._isPaused = false;
11897 this._isTweening = true;
11898
11899 this._timeoutHandler();
11900
11901 return this;
11902 };
11903
11904 /**
11905 * Move the state of the animation to a specific point in the tween's
11906 * timeline. If the animation is not running, this will cause the `step`
11907 * handlers to be called.
11908 * @method seek
11909 * @param {millisecond} millisecond The millisecond of the animation to seek
11910 * to. This must not be less than `0`.
11911 * @chainable
11912 */
11913 NGTweenable.prototype.seek = function (millisecond) {
11914 millisecond = Math.max(millisecond, 0);
11915 var currentTime = now();
11916
11917 if ((this._timestamp + millisecond) === 0) {
11918 return this;
11919 }
11920
11921 this._timestamp = currentTime - millisecond;
11922
11923 if (!this.isPlaying()) {
11924 this._isTweening = true;
11925 this._isPaused = false;
11926
11927 // If the animation is not running, call timeoutHandler to make sure that
11928 // any step handlers are run.
11929 timeoutHandler(this,
11930 this._timestamp,
11931 this._delay,
11932 this._duration,
11933 this._currentState,
11934 this._originalState,
11935 this._targetState,
11936 this._easing,
11937 this._step,
11938 this._scheduleFunction,
11939 currentTime
11940 );
11941
11942 this.pause();
11943 }
11944
11945 return this;
11946 };
11947
11948 /**
11949 * Stops and cancels a tween.
11950 * @param {boolean=} gotoEnd If `false` or omitted, the tween just stops at
11951 * its current state, and the `finish` handler is not invoked. If `true`,
11952 * the tweened object's values are instantly set to the target values, and
11953 * `finish` is invoked.
11954 * @method stop
11955 * @chainable
11956 */
11957 NGTweenable.prototype.stop = function (gotoEnd) {
11958 this._isTweening = false;
11959 this._isPaused = false;
11960 this._timeoutHandler = noop;
11961
11962 (root.cancelAnimationFrame ||
11963 root.webkitCancelAnimationFrame ||
11964 root.oCancelAnimationFrame ||
11965 root.msCancelAnimationFrame ||
11966 root.mozCancelRequestAnimationFrame ||
11967 root.clearTimeout)(this._scheduleId);
11968
11969 if (gotoEnd) {
11970 applyFilter(this, 'beforeTween');
11971 tweenProps(
11972 1,
11973 this._currentState,
11974 this._originalState,
11975 this._targetState,
11976 1,
11977 0,
11978 this._easing
11979 );
11980 applyFilter(this, 'afterTween');
11981 applyFilter(this, 'afterTweenEnd');
11982 this._finish.call(this, this._currentState, this._attachment);
11983 }
11984
11985 return this;
11986 };
11987
11988 /**
11989 * @method isPlaying
11990 * @return {boolean} Whether or not a tween is running.
11991 */
11992 NGTweenable.prototype.isPlaying = function () {
11993 return this._isTweening && !this._isPaused;
11994 };
11995
11996 /**
11997 * Set a custom schedule function.
11998 *
11999 * If a custom function is not set,
12000 * [`requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/Web/API/window.requestAnimationFrame)
12001 * is used if available, otherwise
12002 * [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/Window.setTimeout)
12003 * is used.
12004 * @method setScheduleFunction
12005 * @param {Function(Function,number)} scheduleFunction The function to be
12006 * used to schedule the next frame to be rendered.
12007 */
12008 NGTweenable.prototype.setScheduleFunction = function (scheduleFunction) {
12009 this._scheduleFunction = scheduleFunction;
12010 };
12011
12012 /**
12013 * `delete` all "own" properties. Call this when the `NGTweenable` instance
12014 * is no longer needed to free memory.
12015 * @method dispose
12016 */
12017 NGTweenable.prototype.dispose = function () {
12018 var prop;
12019 for (prop in this) {
12020 if (this.hasOwnProperty(prop)) {
12021 delete this[prop];
12022 }
12023 }
12024 };
12025
12026 /**
12027 * Filters are used for transforming the properties of a tween at various
12028 * points in a NGTweenable's life cycle. See the README for more info on this.
12029 * @private
12030 */
12031 NGTweenable.prototype.filter = {};
12032
12033 /**
12034 * This object contains all of the tweens available to Shifty. It is
12035 * extensible - simply attach properties to the `NGTweenable.prototype.formula`
12036 * Object following the same format as `linear`.
12037 *
12038 * `pos` should be a normalized `number` (between 0 and 1).
12039 * @property formula
12040 * @type {Object(function)}
12041 */
12042 NGTweenable.prototype.formula = {
12043 linear: function (pos) {
12044 return pos;
12045 }
12046 };
12047
12048 formula = NGTweenable.prototype.formula;
12049
12050 shallowCopy(NGTweenable, {
12051 'now': now
12052 ,'each': each
12053 ,'tweenProps': tweenProps
12054 ,'tweenProp': tweenProp
12055 ,'applyFilter': applyFilter
12056 ,'shallowCopy': shallowCopy
12057 ,'defaults': defaults
12058 ,'composeEasingObject': composeEasingObject
12059 });
12060
12061 // `root` is provided in the intro/outro files.
12062
12063 // A hook used for unit testing.
12064 if (typeof SHIFTY_DEBUG_NOW === 'function') {
12065 root.timeoutHandler = timeoutHandler;
12066 }
12067
12068 // Bootstrap NGTweenable appropriately for the environment.
12069 if (typeof exports === 'object') {
12070 // CommonJS
12071 module.exports = NGTweenable;
12072 } else if (typeof define === 'function' && define.amdDISABLED) {
12073 // AMD
12074 define(function () {return NGTweenable;});
12075 } else if (typeof root.NGTweenable === 'undefined') {
12076 // Browser: Make `NGTweenable` globally accessible.
12077 root.NGTweenable = NGTweenable;
12078 }
12079
12080 return NGTweenable;
12081
12082} ());
12083
12084/*!
12085 * All equations are adapted from Thomas Fuchs'
12086 * [Scripty2](https://github.com/madrobby/scripty2/blob/master/src/effects/transitions/penner.js).
12087 *
12088 * Based on Easing Equations (c) 2003 [Robert
12089 * Penner](http://www.robertpenner.com/), all rights reserved. This work is
12090 * [subject to terms](http://www.robertpenner.com/easing_terms_of_use.html).
12091 */
12092
12093/*!
12094 * TERMS OF USE - EASING EQUATIONS
12095 * Open source under the BSD License.
12096 * Easing Equations (c) 2003 Robert Penner, all rights reserved.
12097 */
12098
12099;(function () {
12100
12101 NGTweenable.shallowCopy(NGTweenable.prototype.formula, {
12102 easeInQuad: function (pos) {
12103 return Math.pow(pos, 2);
12104 },
12105
12106 easeOutQuad: function (pos) {
12107 return -(Math.pow((pos - 1), 2) - 1);
12108 },
12109
12110 easeInOutQuad: function (pos) {
12111 if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,2);}
12112 return -0.5 * ((pos -= 2) * pos - 2);
12113 },
12114
12115 easeInCubic: function (pos) {
12116 return Math.pow(pos, 3);
12117 },
12118
12119 easeOutCubic: function (pos) {
12120 return (Math.pow((pos - 1), 3) + 1);
12121 },
12122
12123 easeInOutCubic: function (pos) {
12124 if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,3);}
12125 return 0.5 * (Math.pow((pos - 2),3) + 2);
12126 },
12127
12128 easeInQuart: function (pos) {
12129 return Math.pow(pos, 4);
12130 },
12131
12132 easeOutQuart: function (pos) {
12133 return -(Math.pow((pos - 1), 4) - 1);
12134 },
12135
12136 easeInOutQuart: function (pos) {
12137 if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,4);}
12138 return -0.5 * ((pos -= 2) * Math.pow(pos,3) - 2);
12139 },
12140
12141 easeInQuint: function (pos) {
12142 return Math.pow(pos, 5);
12143 },
12144
12145 easeOutQuint: function (pos) {
12146 return (Math.pow((pos - 1), 5) + 1);
12147 },
12148
12149 easeInOutQuint: function (pos) {
12150 if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,5);}
12151 return 0.5 * (Math.pow((pos - 2),5) + 2);
12152 },
12153
12154 easeInSine: function (pos) {
12155 return -Math.cos(pos * (Math.PI / 2)) + 1;
12156 },
12157
12158 easeOutSine: function (pos) {
12159 return Math.sin(pos * (Math.PI / 2));
12160 },
12161
12162 easeInOutSine: function (pos) {
12163 return (-0.5 * (Math.cos(Math.PI * pos) - 1));
12164 },
12165
12166 easeInExpo: function (pos) {
12167 return (pos === 0) ? 0 : Math.pow(2, 10 * (pos - 1));
12168 },
12169
12170 easeOutExpo: function (pos) {
12171 return (pos === 1) ? 1 : -Math.pow(2, -10 * pos) + 1;
12172 },
12173
12174 easeInOutExpo: function (pos) {
12175 if (pos === 0) {return 0;}
12176 if (pos === 1) {return 1;}
12177 if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(2,10 * (pos - 1));}
12178 return 0.5 * (-Math.pow(2, -10 * --pos) + 2);
12179 },
12180
12181 easeInCirc: function (pos) {
12182 return -(Math.sqrt(1 - (pos * pos)) - 1);
12183 },
12184
12185 easeOutCirc: function (pos) {
12186 return Math.sqrt(1 - Math.pow((pos - 1), 2));
12187 },
12188
12189 easeInOutCirc: function (pos) {
12190 if ((pos /= 0.5) < 1) {return -0.5 * (Math.sqrt(1 - pos * pos) - 1);}
12191 return 0.5 * (Math.sqrt(1 - (pos -= 2) * pos) + 1);
12192 },
12193
12194 easeOutBounce: function (pos) {
12195 if ((pos) < (1 / 2.75)) {
12196 return (7.5625 * pos * pos);
12197 } else if (pos < (2 / 2.75)) {
12198 return (7.5625 * (pos -= (1.5 / 2.75)) * pos + 0.75);
12199 } else if (pos < (2.5 / 2.75)) {
12200 return (7.5625 * (pos -= (2.25 / 2.75)) * pos + 0.9375);
12201 } else {
12202 return (7.5625 * (pos -= (2.625 / 2.75)) * pos + 0.984375);
12203 }
12204 },
12205
12206 easeInBack: function (pos) {
12207 var s = 1.70158;
12208 return (pos) * pos * ((s + 1) * pos - s);
12209 },
12210
12211 easeOutBack: function (pos) {
12212 var s = 1.70158;
12213 return (pos = pos - 1) * pos * ((s + 1) * pos + s) + 1;
12214 },
12215
12216 easeInOutBack: function (pos) {
12217 var s = 1.70158;
12218 if ((pos /= 0.5) < 1) {
12219 return 0.5 * (pos * pos * (((s *= (1.525)) + 1) * pos - s));
12220 }
12221 return 0.5 * ((pos -= 2) * pos * (((s *= (1.525)) + 1) * pos + s) + 2);
12222 },
12223
12224 elastic: function (pos) {
12225 // jshint maxlen:90
12226 return -1 * Math.pow(4,-8 * pos) * Math.sin((pos * 6 - 1) * (2 * Math.PI) / 2) + 1;
12227 },
12228
12229 swingFromTo: function (pos) {
12230 var s = 1.70158;
12231 return ((pos /= 0.5) < 1) ?
12232 0.5 * (pos * pos * (((s *= (1.525)) + 1) * pos - s)) :
12233 0.5 * ((pos -= 2) * pos * (((s *= (1.525)) + 1) * pos + s) + 2);
12234 },
12235
12236 swingFrom: function (pos) {
12237 var s = 1.70158;
12238 return pos * pos * ((s + 1) * pos - s);
12239 },
12240
12241 swingTo: function (pos) {
12242 var s = 1.70158;
12243 return (pos -= 1) * pos * ((s + 1) * pos + s) + 1;
12244 },
12245
12246 bounce: function (pos) {
12247 if (pos < (1 / 2.75)) {
12248 return (7.5625 * pos * pos);
12249 } else if (pos < (2 / 2.75)) {
12250 return (7.5625 * (pos -= (1.5 / 2.75)) * pos + 0.75);
12251 } else if (pos < (2.5 / 2.75)) {
12252 return (7.5625 * (pos -= (2.25 / 2.75)) * pos + 0.9375);
12253 } else {
12254 return (7.5625 * (pos -= (2.625 / 2.75)) * pos + 0.984375);
12255 }
12256 },
12257
12258 bouncePast: function (pos) {
12259 if (pos < (1 / 2.75)) {
12260 return (7.5625 * pos * pos);
12261 } else if (pos < (2 / 2.75)) {
12262 return 2 - (7.5625 * (pos -= (1.5 / 2.75)) * pos + 0.75);
12263 } else if (pos < (2.5 / 2.75)) {
12264 return 2 - (7.5625 * (pos -= (2.25 / 2.75)) * pos + 0.9375);
12265 } else {
12266 return 2 - (7.5625 * (pos -= (2.625 / 2.75)) * pos + 0.984375);
12267 }
12268 },
12269
12270 easeFromTo: function (pos) {
12271 if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,4);}
12272 return -0.5 * ((pos -= 2) * Math.pow(pos,3) - 2);
12273 },
12274
12275 easeFrom: function (pos) {
12276 return Math.pow(pos,4);
12277 },
12278
12279 easeTo: function (pos) {
12280 return Math.pow(pos,0.25);
12281 }
12282 });
12283
12284}());
12285
12286// jshint maxlen:100
12287/**
12288 * The Bezier magic in this file is adapted/copied almost wholesale from
12289 * [Scripty2](https://github.com/madrobby/scripty2/blob/master/src/effects/transitions/cubic-bezier.js),
12290 * which was adapted from Apple code (which probably came from
12291 * [here](http://opensource.apple.com/source/WebCore/WebCore-955.66/platform/graphics/UnitBezier.h)).
12292 * Special thanks to Apple and Thomas Fuchs for much of this code.
12293 */
12294
12295/**
12296 * Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
12297 *
12298 * Redistribution and use in source and binary forms, with or without
12299 * modification, are permitted provided that the following conditions are met:
12300 *
12301 * 1. Redistributions of source code must retain the above copyright notice,
12302 * this list of conditions and the following disclaimer.
12303 *
12304 * 2. Redistributions in binary form must reproduce the above copyright notice,
12305 * this list of conditions and the following disclaimer in the documentation
12306 * and/or other materials provided with the distribution.
12307 *
12308 * 3. Neither the name of the copyright holder(s) nor the names of any
12309 * contributors may be used to endorse or promote products derived from
12310 * this software without specific prior written permission.
12311 *
12312 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
12313 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
12314 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
12315 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
12316 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
12317 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
12318 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
12319 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
12320 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
12321 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
12322 * POSSIBILITY OF SUCH DAMAGE.
12323 */
12324;(function () {
12325 // port of webkit cubic bezier handling by http://www.netzgesta.de/dev/
12326 function cubicBezierAtTime(t,p1x,p1y,p2x,p2y,duration) {
12327 var ax = 0,bx = 0,cx = 0,ay = 0,by = 0,cy = 0;
12328 function sampleCurveX(t) {
12329 return ((ax * t + bx) * t + cx) * t;
12330 }
12331 function sampleCurveY(t) {
12332 return ((ay * t + by) * t + cy) * t;
12333 }
12334 function sampleCurveDerivativeX(t) {
12335 return (3.0 * ax * t + 2.0 * bx) * t + cx;
12336 }
12337 function solveEpsilon(duration) {
12338 return 1.0 / (200.0 * duration);
12339 }
12340 function solve(x,epsilon) {
12341 return sampleCurveY(solveCurveX(x, epsilon));
12342 }
12343 function fabs(n) {
12344 if (n >= 0) {
12345 return n;
12346 } else {
12347 return 0 - n;
12348 }
12349 }
12350 function solveCurveX(x, epsilon) {
12351 var t0,t1,t2,x2,d2,i;
12352 for (t2 = x, i = 0; i < 8; i++) {
12353 x2 = sampleCurveX(t2) - x;
12354 if (fabs(x2) < epsilon) {
12355 return t2;
12356 }
12357 d2 = sampleCurveDerivativeX(t2);
12358 if (fabs(d2) < 1e-6) {
12359 break;
12360 }
12361 t2 = t2 - x2 / d2;
12362 }
12363 t0 = 0.0;
12364 t1 = 1.0;
12365 t2 = x;
12366 if (t2 < t0) {
12367 return t0;
12368 }
12369 if (t2 > t1) {
12370 return t1;
12371 }
12372 while (t0 < t1) {
12373 x2 = sampleCurveX(t2);
12374 if (fabs(x2 - x) < epsilon) {
12375 return t2;
12376 }
12377 if (x > x2) {
12378 t0 = t2;
12379 }else {
12380 t1 = t2;
12381 }
12382 t2 = (t1 - t0) * 0.5 + t0;
12383 }
12384 return t2; // Failure.
12385 }
12386 cx = 3.0 * p1x;
12387 bx = 3.0 * (p2x - p1x) - cx;
12388 ax = 1.0 - cx - bx;
12389 cy = 3.0 * p1y;
12390 by = 3.0 * (p2y - p1y) - cy;
12391 ay = 1.0 - cy - by;
12392 return solve(t, solveEpsilon(duration));
12393 }
12394 /**
12395 * getCubicBezierTransition(x1, y1, x2, y2) -> Function
12396 *
12397 * Generates a transition easing function that is compatible
12398 * with WebKit's CSS transitions `-webkit-transition-timing-function`
12399 * CSS property.
12400 *
12401 * The W3C has more information about CSS3 transition timing functions:
12402 * http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag
12403 *
12404 * @param {number} x1
12405 * @param {number} y1
12406 * @param {number} x2
12407 * @param {number} y2
12408 * @return {function}
12409 * @private
12410 */
12411 function getCubicBezierTransition (x1, y1, x2, y2) {
12412 return function (pos) {
12413 return cubicBezierAtTime(pos,x1,y1,x2,y2,1);
12414 };
12415 }
12416 // End ported code
12417
12418 /**
12419 * Create a Bezier easing function and attach it to `{{#crossLink
12420 * "NGTweenable/formula:property"}}NGTweenable#formula{{/crossLink}}`. This
12421 * function gives you total control over the easing curve. Matthew Lein's
12422 * [Ceaser](http://matthewlein.com/ceaser/) is a useful tool for visualizing
12423 * the curves you can make with this function.
12424 * @method setBezierFunction
12425 * @param {string} name The name of the easing curve. Overwrites the old
12426 * easing function on `{{#crossLink
12427 * "NGTweenable/formula:property"}}NGTweenable#formula{{/crossLink}}` if it
12428 * exists.
12429 * @param {number} x1
12430 * @param {number} y1
12431 * @param {number} x2
12432 * @param {number} y2
12433 * @return {function} The easing function that was attached to
12434 * NGTweenable.prototype.formula.
12435 */
12436 NGTweenable.setBezierFunction = function (name, x1, y1, x2, y2) {
12437 var cubicBezierTransition = getCubicBezierTransition(x1, y1, x2, y2);
12438 cubicBezierTransition.displayName = name;
12439 cubicBezierTransition.x1 = x1;
12440 cubicBezierTransition.y1 = y1;
12441 cubicBezierTransition.x2 = x2;
12442 cubicBezierTransition.y2 = y2;
12443
12444 return NGTweenable.prototype.formula[name] = cubicBezierTransition;
12445 };
12446
12447
12448 /**
12449 * `delete` an easing function from `{{#crossLink
12450 * "NGTweenable/formula:property"}}NGTweenable#formula{{/crossLink}}`. Be
12451 * careful with this method, as it `delete`s whatever easing formula matches
12452 * `name` (which means you can delete standard Shifty easing functions).
12453 * @method unsetBezierFunction
12454 * @param {string} name The name of the easing function to delete.
12455 * @return {function}
12456 */
12457 NGTweenable.unsetBezierFunction = function (name) {
12458 delete NGTweenable.prototype.formula[name];
12459 };
12460
12461})();
12462
12463;(function () {
12464
12465 function getInterpolatedValues (
12466 from, current, targetState, position, easing, delay) {
12467 return NGTweenable.tweenProps(
12468 position, current, from, targetState, 1, delay, easing);
12469 }
12470
12471 // Fake a NGTweenable and patch some internals. This approach allows us to
12472 // skip uneccessary processing and object recreation, cutting down on garbage
12473 // collection pauses.
12474 var mockNGTweenable = new NGTweenable();
12475 mockNGTweenable._filterArgs = [];
12476
12477 /**
12478 * Compute the midpoint of two Objects. This method effectively calculates a
12479 * specific frame of animation that `{{#crossLink
12480 * "NGTweenable/tween:method"}}{{/crossLink}}` does many times over the course
12481 * of a full tween.
12482 *
12483 * var interpolatedValues = NGTweenable.interpolate({
12484 * width: '100px',
12485 * opacity: 0,
12486 * color: '#fff'
12487 * }, {
12488 * width: '200px',
12489 * opacity: 1,
12490 * color: '#000'
12491 * }, 0.5);
12492 *
12493 * console.log(interpolatedValues);
12494 * // {opacity: 0.5, width: "150px", color: "rgb(127,127,127)"}
12495 *
12496 * @static
12497 * @method interpolate
12498 * @param {Object} from The starting values to tween from.
12499 * @param {Object} targetState The ending values to tween to.
12500 * @param {number} position The normalized position value (between `0.0` and
12501 * `1.0`) to interpolate the values between `from` and `to` for. `from`
12502 * represents `0` and `to` represents `1`.
12503 * @param {Object.<string|Function>|string|Function} easing The easing
12504 * curve(s) to calculate the midpoint against. You can reference any easing
12505 * function attached to `NGTweenable.prototype.formula`, or provide the easing
12506 * function(s) directly. If omitted, this defaults to "linear".
12507 * @param {number=} opt_delay Optional delay to pad the beginning of the
12508 * interpolated tween with. This increases the range of `position` from (`0`
12509 * through `1`) to (`0` through `1 + opt_delay`). So, a delay of `0.5` would
12510 * increase all valid values of `position` to numbers between `0` and `1.5`.
12511 * @return {Object}
12512 */
12513 NGTweenable.interpolate = function (
12514 from, targetState, position, easing, opt_delay) {
12515
12516 var current = NGTweenable.shallowCopy({}, from);
12517 var delay = opt_delay || 0;
12518 var easingObject = NGTweenable.composeEasingObject(
12519 from, easing || 'linear');
12520
12521 mockNGTweenable.set({});
12522
12523 // Alias and reuse the _filterArgs array instead of recreating it.
12524 var filterArgs = mockNGTweenable._filterArgs;
12525 filterArgs.length = 0;
12526 filterArgs[0] = current;
12527 filterArgs[1] = from;
12528 filterArgs[2] = targetState;
12529 filterArgs[3] = easingObject;
12530
12531 // Any defined value transformation must be applied
12532 NGTweenable.applyFilter(mockNGTweenable, 'tweenCreated');
12533 NGTweenable.applyFilter(mockNGTweenable, 'beforeTween');
12534
12535 var interpolatedValues = getInterpolatedValues(
12536 from, current, targetState, position, easingObject, delay);
12537
12538 // Transform values back into their original format
12539 NGTweenable.applyFilter(mockNGTweenable, 'afterTween');
12540
12541 return interpolatedValues;
12542 };
12543
12544}());
12545
12546/**
12547 * This module adds string interpolation support to Shifty.
12548 *
12549 * The Token extension allows Shifty to tween numbers inside of strings. Among
12550 * other things, this allows you to animate CSS properties. For example, you
12551 * can do this:
12552 *
12553 * var tweenable = new NGTweenable();
12554 * tweenable.tween({
12555 * from: { transform: 'translateX(45px)' },
12556 * to: { transform: 'translateX(90xp)' }
12557 * });
12558 *
12559 * `translateX(45)` will be tweened to `translateX(90)`. To demonstrate:
12560 *
12561 * var tweenable = new NGTweenable();
12562 * tweenable.tween({
12563 * from: { transform: 'translateX(45px)' },
12564 * to: { transform: 'translateX(90px)' },
12565 * step: function (state) {
12566 * console.log(state.transform);
12567 * }
12568 * });
12569 *
12570 * The above snippet will log something like this in the console:
12571 *
12572 * translateX(60.3px)
12573 * ...
12574 * translateX(76.05px)
12575 * ...
12576 * translateX(90px)
12577 *
12578 * Another use for this is animating colors:
12579 *
12580 * var tweenable = new NGTweenable();
12581 * tweenable.tween({
12582 * from: { color: 'rgb(0,255,0)' },
12583 * to: { color: 'rgb(255,0,255)' },
12584 * step: function (state) {
12585 * console.log(state.color);
12586 * }
12587 * });
12588 *
12589 * The above snippet will log something like this:
12590 *
12591 * rgb(84,170,84)
12592 * ...
12593 * rgb(170,84,170)
12594 * ...
12595 * rgb(255,0,255)
12596 *
12597 * This extension also supports hexadecimal colors, in both long (`#ff00ff`)
12598 * and short (`#f0f`) forms. Be aware that hexadecimal input values will be
12599 * converted into the equivalent RGB output values. This is done to optimize
12600 * for performance.
12601 *
12602 * var tweenable = new NGTweenable();
12603 * tweenable.tween({
12604 * from: { color: '#0f0' },
12605 * to: { color: '#f0f' },
12606 * step: function (state) {
12607 * console.log(state.color);
12608 * }
12609 * });
12610 *
12611 * This snippet will generate the same output as the one before it because
12612 * equivalent values were supplied (just in hexadecimal form rather than RGB):
12613 *
12614 * rgb(84,170,84)
12615 * ...
12616 * rgb(170,84,170)
12617 * ...
12618 * rgb(255,0,255)
12619 *
12620 * ## Easing support
12621 *
12622 * Easing works somewhat differently in the Token extension. This is because
12623 * some CSS properties have multiple values in them, and you might need to
12624 * tween each value along its own easing curve. A basic example:
12625 *
12626 * var tweenable = new NGTweenable();
12627 * tweenable.tween({
12628 * from: { transform: 'translateX(0px) translateY(0px)' },
12629 * to: { transform: 'translateX(100px) translateY(100px)' },
12630 * easing: { transform: 'easeInQuad' },
12631 * step: function (state) {
12632 * console.log(state.transform);
12633 * }
12634 * });
12635 *
12636 * The above snippet will create values like this:
12637 *
12638 * translateX(11.56px) translateY(11.56px)
12639 * ...
12640 * translateX(46.24px) translateY(46.24px)
12641 * ...
12642 * translateX(100px) translateY(100px)
12643 *
12644 * In this case, the values for `translateX` and `translateY` are always the
12645 * same for each step of the tween, because they have the same start and end
12646 * points and both use the same easing curve. We can also tween `translateX`
12647 * and `translateY` along independent curves:
12648 *
12649 * var tweenable = new NGTweenable();
12650 * tweenable.tween({
12651 * from: { transform: 'translateX(0px) translateY(0px)' },
12652 * to: { transform: 'translateX(100px) translateY(100px)' },
12653 * easing: { transform: 'easeInQuad bounce' },
12654 * step: function (state) {
12655 * console.log(state.transform);
12656 * }
12657 * });
12658 *
12659 * The above snippet will create values like this:
12660 *
12661 * translateX(10.89px) translateY(82.35px)
12662 * ...
12663 * translateX(44.89px) translateY(86.73px)
12664 * ...
12665 * translateX(100px) translateY(100px)
12666 *
12667 * `translateX` and `translateY` are not in sync anymore, because `easeInQuad`
12668 * was specified for `translateX` and `bounce` for `translateY`. Mixing and
12669 * matching easing curves can make for some interesting motion in your
12670 * animations.
12671 *
12672 * The order of the space-separated easing curves correspond the token values
12673 * they apply to. If there are more token values than easing curves listed,
12674 * the last easing curve listed is used.
12675 * @submodule NGTweenable.token
12676 */
12677
12678// token function is defined above only so that dox-foundation sees it as
12679// documentation and renders it. It is never used, and is optimized away at
12680// build time.
12681
12682;(function (NGTweenable) {
12683
12684 /**
12685 * @typedef {{
12686 * formatString: string
12687 * chunkNames: Array.<string>
12688 * }}
12689 * @private
12690 */
12691 var formatManifest;
12692
12693 // CONSTANTS
12694
12695 var R_NUMBER_COMPONENT = /(\d|\-|\.)/;
12696 var R_FORMAT_CHUNKS = /([^\-0-9\.]+)/g;
12697 var R_UNFORMATTED_VALUES = /[0-9.\-]+/g;
12698 var R_RGB = new RegExp(
12699 'rgb\\(' + R_UNFORMATTED_VALUES.source +
12700 (/,\s*/.source) + R_UNFORMATTED_VALUES.source +
12701 (/,\s*/.source) + R_UNFORMATTED_VALUES.source + '\\)', 'g');
12702 var R_RGB_PREFIX = /^.*\(/;
12703 var R_HEX = /#([0-9]|[a-f]){3,6}/gi;
12704 var VALUE_PLACEHOLDER = 'VAL';
12705
12706 // HELPERS
12707
12708 /**
12709 * @param {Array.number} rawValues
12710 * @param {string} prefix
12711 *
12712 * @return {Array.<string>}
12713 * @private
12714 */
12715 function getFormatChunksFrom (rawValues, prefix) {
12716 var accumulator = [];
12717
12718 var rawValuesLength = rawValues.length;
12719 var i;
12720
12721 for (i = 0; i < rawValuesLength; i++) {
12722 accumulator.push('_' + prefix + '_' + i);
12723 }
12724
12725 return accumulator;
12726 }
12727
12728 /**
12729 * @param {string} formattedString
12730 *
12731 * @return {string}
12732 * @private
12733 */
12734 function getFormatStringFrom (formattedString) {
12735 var chunks = formattedString.match(R_FORMAT_CHUNKS);
12736
12737 if (!chunks) {
12738 // chunks will be null if there were no tokens to parse in
12739 // formattedString (for example, if formattedString is '2'). Coerce
12740 // chunks to be useful here.
12741 chunks = ['', ''];
12742
12743 // If there is only one chunk, assume that the string is a number
12744 // followed by a token...
12745 // NOTE: This may be an unwise assumption.
12746 } else if (chunks.length === 1 ||
12747 // ...or if the string starts with a number component (".", "-", or a
12748 // digit)...
12749 formattedString.charAt(0).match(R_NUMBER_COMPONENT)) {
12750 // ...prepend an empty string here to make sure that the formatted number
12751 // is properly replaced by VALUE_PLACEHOLDER
12752 chunks.unshift('');
12753 }
12754
12755 return chunks.join(VALUE_PLACEHOLDER);
12756 }
12757
12758 /**
12759 * Convert all hex color values within a string to an rgb string.
12760 *
12761 * @param {Object} stateObject
12762 *
12763 * @return {Object} The modified obj
12764 * @private
12765 */
12766 function sanitizeObjectForHexProps (stateObject) {
12767 NGTweenable.each(stateObject, function (prop) {
12768 var currentProp = stateObject[prop];
12769
12770 if (typeof currentProp === 'string' && currentProp.match(R_HEX)) {
12771 stateObject[prop] = sanitizeHexChunksToRGB(currentProp);
12772 }
12773 });
12774 }
12775
12776 /**
12777 * @param {string} str
12778 *
12779 * @return {string}
12780 * @private
12781 */
12782 function sanitizeHexChunksToRGB (str) {
12783 return filterStringChunks(R_HEX, str, convertHexToRGB);
12784 }
12785
12786 /**
12787 * @param {string} hexString
12788 *
12789 * @return {string}
12790 * @private
12791 */
12792 function convertHexToRGB (hexString) {
12793 var rgbArr = hexToRGBArray(hexString);
12794 return 'rgb(' + rgbArr[0] + ',' + rgbArr[1] + ',' + rgbArr[2] + ')';
12795 }
12796
12797 var hexToRGBArray_returnArray = [];
12798 /**
12799 * Convert a hexadecimal string to an array with three items, one each for
12800 * the red, blue, and green decimal values.
12801 *
12802 * @param {string} hex A hexadecimal string.
12803 *
12804 * @returns {Array.<number>} The converted Array of RGB values if `hex` is a
12805 * valid string, or an Array of three 0's.
12806 * @private
12807 */
12808 function hexToRGBArray (hex) {
12809
12810 hex = hex.replace(/#/, '');
12811
12812 // If the string is a shorthand three digit hex notation, normalize it to
12813 // the standard six digit notation
12814 if (hex.length === 3) {
12815 hex = hex.split('');
12816 hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
12817 }
12818
12819 hexToRGBArray_returnArray[0] = hexToDec(hex.substr(0, 2));
12820 hexToRGBArray_returnArray[1] = hexToDec(hex.substr(2, 2));
12821 hexToRGBArray_returnArray[2] = hexToDec(hex.substr(4, 2));
12822
12823 return hexToRGBArray_returnArray;
12824 }
12825
12826 /**
12827 * Convert a base-16 number to base-10.
12828 *
12829 * @param {Number|String} hex The value to convert
12830 *
12831 * @returns {Number} The base-10 equivalent of `hex`.
12832 * @private
12833 */
12834 function hexToDec (hex) {
12835 return parseInt(hex, 16);
12836 }
12837
12838 /**
12839 * Runs a filter operation on all chunks of a string that match a RegExp
12840 *
12841 * @param {RegExp} pattern
12842 * @param {string} unfilteredString
12843 * @param {function(string)} filter
12844 *
12845 * @return {string}
12846 * @private
12847 */
12848 function filterStringChunks (pattern, unfilteredString, filter) {
12849 var pattenMatches = unfilteredString.match(pattern);
12850 var filteredString = unfilteredString.replace(pattern, VALUE_PLACEHOLDER);
12851
12852 if (pattenMatches) {
12853 var pattenMatchesLength = pattenMatches.length;
12854 var currentChunk;
12855
12856 for (var i = 0; i < pattenMatchesLength; i++) {
12857 currentChunk = pattenMatches.shift();
12858 filteredString = filteredString.replace(
12859 VALUE_PLACEHOLDER, filter(currentChunk));
12860 }
12861 }
12862
12863 return filteredString;
12864 }
12865
12866 /**
12867 * Check for floating point values within rgb strings and rounds them.
12868 *
12869 * @param {string} formattedString
12870 *
12871 * @return {string}
12872 * @private
12873 */
12874 function sanitizeRGBChunks (formattedString) {
12875 return filterStringChunks(R_RGB, formattedString, sanitizeRGBChunk);
12876 }
12877
12878 /**
12879 * @param {string} rgbChunk
12880 *
12881 * @return {string}
12882 * @private
12883 */
12884 function sanitizeRGBChunk (rgbChunk) {
12885 var numbers = rgbChunk.match(R_UNFORMATTED_VALUES);
12886 var numbersLength = numbers.length;
12887 var sanitizedString = rgbChunk.match(R_RGB_PREFIX)[0];
12888
12889 for (var i = 0; i < numbersLength; i++) {
12890 sanitizedString += parseInt(numbers[i], 10) + ',';
12891 }
12892
12893 sanitizedString = sanitizedString.slice(0, -1) + ')';
12894
12895 return sanitizedString;
12896 }
12897
12898 /**
12899 * @param {Object} stateObject
12900 *
12901 * @return {Object} An Object of formatManifests that correspond to
12902 * the string properties of stateObject
12903 * @private
12904 */
12905 function getFormatManifests (stateObject) {
12906 var manifestAccumulator = {};
12907
12908 NGTweenable.each(stateObject, function (prop) {
12909 var currentProp = stateObject[prop];
12910
12911 if (typeof currentProp === 'string') {
12912 var rawValues = getValuesFrom(currentProp);
12913
12914 manifestAccumulator[prop] = {
12915 'formatString': getFormatStringFrom(currentProp)
12916 ,'chunkNames': getFormatChunksFrom(rawValues, prop)
12917 };
12918 }
12919 });
12920
12921 return manifestAccumulator;
12922 }
12923
12924 /**
12925 * @param {Object} stateObject
12926 * @param {Object} formatManifests
12927 * @private
12928 */
12929 function expandFormattedProperties (stateObject, formatManifests) {
12930 NGTweenable.each(formatManifests, function (prop) {
12931 var currentProp = stateObject[prop];
12932 var rawValues = getValuesFrom(currentProp);
12933 var rawValuesLength = rawValues.length;
12934
12935 for (var i = 0; i < rawValuesLength; i++) {
12936 stateObject[formatManifests[prop].chunkNames[i]] = +rawValues[i];
12937 }
12938
12939 delete stateObject[prop];
12940 });
12941 }
12942
12943 /**
12944 * @param {Object} stateObject
12945 * @param {Object} formatManifests
12946 * @private
12947 */
12948 function collapseFormattedProperties (stateObject, formatManifests) {
12949 NGTweenable.each(formatManifests, function (prop) {
12950 var currentProp = stateObject[prop];
12951 var formatChunks = extractPropertyChunks(
12952 stateObject, formatManifests[prop].chunkNames);
12953 var valuesList = getValuesList(
12954 formatChunks, formatManifests[prop].chunkNames);
12955 currentProp = getFormattedValues(
12956 formatManifests[prop].formatString, valuesList);
12957 stateObject[prop] = sanitizeRGBChunks(currentProp);
12958 });
12959 }
12960
12961 /**
12962 * @param {Object} stateObject
12963 * @param {Array.<string>} chunkNames
12964 *
12965 * @return {Object} The extracted value chunks.
12966 * @private
12967 */
12968 function extractPropertyChunks (stateObject, chunkNames) {
12969 var extractedValues = {};
12970 var currentChunkName, chunkNamesLength = chunkNames.length;
12971
12972 for (var i = 0; i < chunkNamesLength; i++) {
12973 currentChunkName = chunkNames[i];
12974 extractedValues[currentChunkName] = stateObject[currentChunkName];
12975 delete stateObject[currentChunkName];
12976 }
12977
12978 return extractedValues;
12979 }
12980
12981 var getValuesList_accumulator = [];
12982 /**
12983 * @param {Object} stateObject
12984 * @param {Array.<string>} chunkNames
12985 *
12986 * @return {Array.<number>}
12987 * @private
12988 */
12989 function getValuesList (stateObject, chunkNames) {
12990 getValuesList_accumulator.length = 0;
12991 var chunkNamesLength = chunkNames.length;
12992
12993 for (var i = 0; i < chunkNamesLength; i++) {
12994 getValuesList_accumulator.push(stateObject[chunkNames[i]]);
12995 }
12996
12997 return getValuesList_accumulator;
12998 }
12999
13000 /**
13001 * @param {string} formatString
13002 * @param {Array.<number>} rawValues
13003 *
13004 * @return {string}
13005 * @private
13006 */
13007 function getFormattedValues (formatString, rawValues) {
13008 var formattedValueString = formatString;
13009 var rawValuesLength = rawValues.length;
13010
13011 for (var i = 0; i < rawValuesLength; i++) {
13012 formattedValueString = formattedValueString.replace(
13013 VALUE_PLACEHOLDER, +rawValues[i].toFixed(4));
13014 }
13015
13016 return formattedValueString;
13017 }
13018
13019 /**
13020 * Note: It's the duty of the caller to convert the Array elements of the
13021 * return value into numbers. This is a performance optimization.
13022 *
13023 * @param {string} formattedString
13024 *
13025 * @return {Array.<string>|null}
13026 * @private
13027 */
13028 function getValuesFrom (formattedString) {
13029 return formattedString.match(R_UNFORMATTED_VALUES);
13030 }
13031
13032 /**
13033 * @param {Object} easingObject
13034 * @param {Object} tokenData
13035 * @private
13036 */
13037 function expandEasingObject (easingObject, tokenData) {
13038 NGTweenable.each(tokenData, function (prop) {
13039 var currentProp = tokenData[prop];
13040 var chunkNames = currentProp.chunkNames;
13041 var chunkLength = chunkNames.length;
13042
13043 var easing = easingObject[prop];
13044 var i;
13045
13046 if (typeof easing === 'string') {
13047 var easingChunks = easing.split(' ');
13048 var lastEasingChunk = easingChunks[easingChunks.length - 1];
13049
13050 for (i = 0; i < chunkLength; i++) {
13051 easingObject[chunkNames[i]] = easingChunks[i] || lastEasingChunk;
13052 }
13053
13054 } else {
13055 for (i = 0; i < chunkLength; i++) {
13056 easingObject[chunkNames[i]] = easing;
13057 }
13058 }
13059
13060 delete easingObject[prop];
13061 });
13062 }
13063
13064 /**
13065 * @param {Object} easingObject
13066 * @param {Object} tokenData
13067 * @private
13068 */
13069 function collapseEasingObject (easingObject, tokenData) {
13070 NGTweenable.each(tokenData, function (prop) {
13071 var currentProp = tokenData[prop];
13072 var chunkNames = currentProp.chunkNames;
13073 var chunkLength = chunkNames.length;
13074
13075 var firstEasing = easingObject[chunkNames[0]];
13076 var typeofEasings = typeof firstEasing;
13077
13078 if (typeofEasings === 'string') {
13079 var composedEasingString = '';
13080
13081 for (var i = 0; i < chunkLength; i++) {
13082 composedEasingString += ' ' + easingObject[chunkNames[i]];
13083 delete easingObject[chunkNames[i]];
13084 }
13085
13086 easingObject[prop] = composedEasingString.substr(1);
13087 } else {
13088 easingObject[prop] = firstEasing;
13089 }
13090 });
13091 }
13092
13093 NGTweenable.prototype.filter.token = {
13094 'tweenCreated': function (currentState, fromState, toState, easingObject) {
13095 sanitizeObjectForHexProps(currentState);
13096 sanitizeObjectForHexProps(fromState);
13097 sanitizeObjectForHexProps(toState);
13098 this._tokenData = getFormatManifests(currentState);
13099 },
13100
13101 'beforeTween': function (currentState, fromState, toState, easingObject) {
13102 expandEasingObject(easingObject, this._tokenData);
13103 expandFormattedProperties(currentState, this._tokenData);
13104 expandFormattedProperties(fromState, this._tokenData);
13105 expandFormattedProperties(toState, this._tokenData);
13106 },
13107
13108 'afterTween': function (currentState, fromState, toState, easingObject) {
13109 collapseFormattedProperties(currentState, this._tokenData);
13110 collapseFormattedProperties(fromState, this._tokenData);
13111 collapseFormattedProperties(toState, this._tokenData);
13112 collapseEasingObject(easingObject, this._tokenData);
13113 }
13114 };
13115
13116} (NGTweenable));
13117
13118}).call(null);
13119
13120
13121
13122
13123//##########################################################################################################################
13124//## HAMMER.JS #############################################################################################################
13125//##########################################################################################################################
13126
13127// HAMMER.JS
13128
13129// external module EMBEDED in nanogallery
13130// NGY BUILD:
13131// replace "Hammer" with "NGHammer" (case sensitive)
13132// replace "var SUPPORT_POINTER_EVENTS = prefixed(window, 'PointerEvent') !== undefined;" with "var SUPPORT_POINTER_EVENTS = false;"
13133// replace "define.amd" with "define.amdDISABLED"
13134
13135
13136
13137/*! NGHammer.JS - v2.0.7 - 2016-04-22
13138 * http://hammerjs.github.io/
13139 *
13140 * Copyright (c) 2016 Jorik Tangelder;
13141 * Licensed under the MIT license */
13142(function(window, document, exportName, undefined) {
13143 'use strict';
13144
13145var VENDOR_PREFIXES = ['', 'webkit', 'Moz', 'MS', 'ms', 'o'];
13146var TEST_ELEMENT = document.createElement('div');
13147
13148var TYPE_FUNCTION = 'function';
13149
13150var round = Math.round;
13151var abs = Math.abs;
13152var now = Date.now;
13153
13154/**
13155 * set a timeout with a given scope
13156 * @param {Function} fn
13157 * @param {Number} timeout
13158 * @param {Object} context
13159 * @returns {number}
13160 */
13161function setTimeoutContext(fn, timeout, context) {
13162 return setTimeout(bindFn(fn, context), timeout);
13163}
13164
13165/**
13166 * if the argument is an array, we want to execute the fn on each entry
13167 * if it aint an array we don't want to do a thing.
13168 * this is used by all the methods that accept a single and array argument.
13169 * @param {*|Array} arg
13170 * @param {String} fn
13171 * @param {Object} [context]
13172 * @returns {Boolean}
13173 */
13174function invokeArrayArg(arg, fn, context) {
13175 if (Array.isArray(arg)) {
13176 each(arg, context[fn], context);
13177 return true;
13178 }
13179 return false;
13180}
13181
13182/**
13183 * walk objects and arrays
13184 * @param {Object} obj
13185 * @param {Function} iterator
13186 * @param {Object} context
13187 */
13188function each(obj, iterator, context) {
13189 var i;
13190
13191 if (!obj) {
13192 return;
13193 }
13194
13195 if (obj.forEach) {
13196 obj.forEach(iterator, context);
13197 } else if (obj.length !== undefined) {
13198 i = 0;
13199 while (i < obj.length) {
13200 iterator.call(context, obj[i], i, obj);
13201 i++;
13202 }
13203 } else {
13204 for (i in obj) {
13205 obj.hasOwnProperty(i) && iterator.call(context, obj[i], i, obj);
13206 }
13207 }
13208}
13209
13210/**
13211 * wrap a method with a deprecation warning and stack trace
13212 * @param {Function} method
13213 * @param {String} name
13214 * @param {String} message
13215 * @returns {Function} A new function wrapping the supplied method.
13216 */
13217function deprecate(method, name, message) {
13218 var deprecationMessage = 'DEPRECATED METHOD: ' + name + '\n' + message + ' AT \n';
13219 return function() {
13220 var e = new Error('get-stack-trace');
13221 var stack = e && e.stack ? e.stack.replace(/^[^\(]+?[\n$]/gm, '')
13222 .replace(/^\s+at\s+/gm, '')
13223 .replace(/^Object.<anonymous>\s*\(/gm, '{anonymous}()@') : 'Unknown Stack Trace';
13224
13225 var log = window.console && (window.console.warn || window.console.log);
13226 if (log) {
13227 log.call(window.console, deprecationMessage, stack);
13228 }
13229 return method.apply(this, arguments);
13230 };
13231}
13232
13233/**
13234 * extend object.
13235 * means that properties in dest will be overwritten by the ones in src.
13236 * @param {Object} target
13237 * @param {...Object} objects_to_assign
13238 * @returns {Object} target
13239 */
13240var assign;
13241if (typeof Object.assign !== 'function') {
13242 assign = function assign(target) {
13243 if (target === undefined || target === null) {
13244 throw new TypeError('Cannot convert undefined or null to object');
13245 }
13246
13247 var output = Object(target);
13248 for (var index = 1; index < arguments.length; index++) {
13249 var source = arguments[index];
13250 if (source !== undefined && source !== null) {
13251 for (var nextKey in source) {
13252 if (source.hasOwnProperty(nextKey)) {
13253 output[nextKey] = source[nextKey];
13254 }
13255 }
13256 }
13257 }
13258 return output;
13259 };
13260} else {
13261 assign = Object.assign;
13262}
13263
13264/**
13265 * extend object.
13266 * means that properties in dest will be overwritten by the ones in src.
13267 * @param {Object} dest
13268 * @param {Object} src
13269 * @param {Boolean} [merge=false]
13270 * @returns {Object} dest
13271 */
13272var extend = deprecate(function extend(dest, src, merge) {
13273 var keys = Object.keys(src);
13274 var i = 0;
13275 while (i < keys.length) {
13276 if (!merge || (merge && dest[keys[i]] === undefined)) {
13277 dest[keys[i]] = src[keys[i]];
13278 }
13279 i++;
13280 }
13281 return dest;
13282}, 'extend', 'Use `assign`.');
13283
13284/**
13285 * merge the values from src in the dest.
13286 * means that properties that exist in dest will not be overwritten by src
13287 * @param {Object} dest
13288 * @param {Object} src
13289 * @returns {Object} dest
13290 */
13291var merge = deprecate(function merge(dest, src) {
13292 return extend(dest, src, true);
13293}, 'merge', 'Use `assign`.');
13294
13295/**
13296 * simple class inheritance
13297 * @param {Function} child
13298 * @param {Function} base
13299 * @param {Object} [properties]
13300 */
13301function inherit(child, base, properties) {
13302 var baseP = base.prototype,
13303 childP;
13304
13305 childP = child.prototype = Object.create(baseP);
13306 childP.constructor = child;
13307 childP._super = baseP;
13308
13309 if (properties) {
13310 assign(childP, properties);
13311 }
13312}
13313
13314/**
13315 * simple function bind
13316 * @param {Function} fn
13317 * @param {Object} context
13318 * @returns {Function}
13319 */
13320function bindFn(fn, context) {
13321 return function boundFn() {
13322 return fn.apply(context, arguments);
13323 };
13324}
13325
13326/**
13327 * let a boolean value also be a function that must return a boolean
13328 * this first item in args will be used as the context
13329 * @param {Boolean|Function} val
13330 * @param {Array} [args]
13331 * @returns {Boolean}
13332 */
13333function boolOrFn(val, args) {
13334 if (typeof val == TYPE_FUNCTION) {
13335 return val.apply(args ? args[0] || undefined : undefined, args);
13336 }
13337 return val;
13338}
13339
13340/**
13341 * use the val2 when val1 is undefined
13342 * @param {*} val1
13343 * @param {*} val2
13344 * @returns {*}
13345 */
13346function ifUndefined(val1, val2) {
13347 return (val1 === undefined) ? val2 : val1;
13348}
13349
13350/**
13351 * addEventListener with multiple events at once
13352 * @param {EventTarget} target
13353 * @param {String} types
13354 * @param {Function} handler
13355 */
13356function addEventListeners(target, types, handler) {
13357 each(splitStr(types), function(type) {
13358 target.addEventListener(type, handler, false);
13359 });
13360}
13361
13362/**
13363 * removeEventListener with multiple events at once
13364 * @param {EventTarget} target
13365 * @param {String} types
13366 * @param {Function} handler
13367 */
13368function removeEventListeners(target, types, handler) {
13369 each(splitStr(types), function(type) {
13370 target.removeEventListener(type, handler, false);
13371 });
13372}
13373
13374/**
13375 * find if a node is in the given parent
13376 * @method hasParent
13377 * @param {HTMLElement} node
13378 * @param {HTMLElement} parent
13379 * @return {Boolean} found
13380 */
13381function hasParent(node, parent) {
13382 while (node) {
13383 if (node == parent) {
13384 return true;
13385 }
13386 node = node.parentNode;
13387 }
13388 return false;
13389}
13390
13391/**
13392 * small indexOf wrapper
13393 * @param {String} str
13394 * @param {String} find
13395 * @returns {Boolean} found
13396 */
13397function inStr(str, find) {
13398 return str.indexOf(find) > -1;
13399}
13400
13401/**
13402 * split string on whitespace
13403 * @param {String} str
13404 * @returns {Array} words
13405 */
13406function splitStr(str) {
13407 return str.trim().split(/\s+/g);
13408}
13409
13410/**
13411 * find if a array contains the object using indexOf or a simple polyFill
13412 * @param {Array} src
13413 * @param {String} find
13414 * @param {String} [findByKey]
13415 * @return {Boolean|Number} false when not found, or the index
13416 */
13417function inArray(src, find, findByKey) {
13418 if (src.indexOf && !findByKey) {
13419 return src.indexOf(find);
13420 } else {
13421 var i = 0;
13422 while (i < src.length) {
13423 if ((findByKey && src[i][findByKey] == find) || (!findByKey && src[i] === find)) {
13424 return i;
13425 }
13426 i++;
13427 }
13428 return -1;
13429 }
13430}
13431
13432/**
13433 * convert array-like objects to real arrays
13434 * @param {Object} obj
13435 * @returns {Array}
13436 */
13437function toArray(obj) {
13438 return Array.prototype.slice.call(obj, 0);
13439}
13440
13441/**
13442 * unique array with objects based on a key (like 'id') or just by the array's value
13443 * @param {Array} src [{id:1},{id:2},{id:1}]
13444 * @param {String} [key]
13445 * @param {Boolean} [sort=False]
13446 * @returns {Array} [{id:1},{id:2}]
13447 */
13448function uniqueArray(src, key, sort) {
13449 var results = [];
13450 var values = [];
13451 var i = 0;
13452
13453 while (i < src.length) {
13454 var val = key ? src[i][key] : src[i];
13455 if (inArray(values, val) < 0) {
13456 results.push(src[i]);
13457 }
13458 values[i] = val;
13459 i++;
13460 }
13461
13462 if (sort) {
13463 if (!key) {
13464 results = results.sort();
13465 } else {
13466 results = results.sort(function sortUniqueArray(a, b) {
13467 return a[key] > b[key];
13468 });
13469 }
13470 }
13471
13472 return results;
13473}
13474
13475/**
13476 * get the prefixed property
13477 * @param {Object} obj
13478 * @param {String} property
13479 * @returns {String|Undefined} prefixed
13480 */
13481function prefixed(obj, property) {
13482 var prefix, prop;
13483 var camelProp = property[0].toUpperCase() + property.slice(1);
13484
13485 var i = 0;
13486 while (i < VENDOR_PREFIXES.length) {
13487 prefix = VENDOR_PREFIXES[i];
13488 prop = (prefix) ? prefix + camelProp : property;
13489
13490 if (prop in obj) {
13491 return prop;
13492 }
13493 i++;
13494 }
13495 return undefined;
13496}
13497
13498/**
13499 * get a unique id
13500 * @returns {number} uniqueId
13501 */
13502var _uniqueId = 1;
13503function uniqueId() {
13504 return _uniqueId++;
13505}
13506
13507/**
13508 * get the window object of an element
13509 * @param {HTMLElement} element
13510 * @returns {DocumentView|Window}
13511 */
13512function getWindowForElement(element) {
13513 var doc = element.ownerDocument || element;
13514 return (doc.defaultView || doc.parentWindow || window);
13515}
13516
13517var MOBILE_REGEX = /mobile|tablet|ip(ad|hone|od)|android/i;
13518
13519var SUPPORT_TOUCH = ('ontouchstart' in window);
13520// var SUPPORT_POINTER_EVENTS = prefixed(window, 'PointerEvent') !== undefined;
13521var SUPPORT_POINTER_EVENTS = false;
13522var SUPPORT_ONLY_TOUCH = SUPPORT_TOUCH && MOBILE_REGEX.test(navigator.userAgent);
13523
13524var INPUT_TYPE_TOUCH = 'touch';
13525var INPUT_TYPE_PEN = 'pen';
13526var INPUT_TYPE_MOUSE = 'mouse';
13527var INPUT_TYPE_KINECT = 'kinect';
13528
13529var COMPUTE_INTERVAL = 25;
13530
13531var INPUT_START = 1;
13532var INPUT_MOVE = 2;
13533var INPUT_END = 4;
13534var INPUT_CANCEL = 8;
13535
13536var DIRECTION_NONE = 1;
13537var DIRECTION_LEFT = 2;
13538var DIRECTION_RIGHT = 4;
13539var DIRECTION_UP = 8;
13540var DIRECTION_DOWN = 16;
13541
13542var DIRECTION_HORIZONTAL = DIRECTION_LEFT | DIRECTION_RIGHT;
13543var DIRECTION_VERTICAL = DIRECTION_UP | DIRECTION_DOWN;
13544var DIRECTION_ALL = DIRECTION_HORIZONTAL | DIRECTION_VERTICAL;
13545
13546var PROPS_XY = ['x', 'y'];
13547var PROPS_CLIENT_XY = ['clientX', 'clientY'];
13548
13549/**
13550 * create new input type manager
13551 * @param {Manager} manager
13552 * @param {Function} callback
13553 * @returns {Input}
13554 * @constructor
13555 */
13556function Input(manager, callback) {
13557 var self = this;
13558 this.manager = manager;
13559 this.callback = callback;
13560 this.element = manager.element;
13561 this.target = manager.options.inputTarget;
13562
13563 // smaller wrapper around the handler, for the scope and the enabled state of the manager,
13564 // so when disabled the input events are completely bypassed.
13565 this.domHandler = function(ev) {
13566 if (boolOrFn(manager.options.enable, [manager])) {
13567 self.handler(ev);
13568 }
13569 };
13570
13571 this.init();
13572
13573}
13574
13575Input.prototype = {
13576 /**
13577 * should handle the inputEvent data and trigger the callback
13578 * @virtual
13579 */
13580 handler: function() { },
13581
13582 /**
13583 * bind the events
13584 */
13585 init: function() {
13586 this.evEl && addEventListeners(this.element, this.evEl, this.domHandler);
13587 this.evTarget && addEventListeners(this.target, this.evTarget, this.domHandler);
13588 this.evWin && addEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler);
13589 },
13590
13591 /**
13592 * unbind the events
13593 */
13594 destroy: function() {
13595 this.evEl && removeEventListeners(this.element, this.evEl, this.domHandler);
13596 this.evTarget && removeEventListeners(this.target, this.evTarget, this.domHandler);
13597 this.evWin && removeEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler);
13598 }
13599};
13600
13601/**
13602 * create new input type manager
13603 * called by the Manager constructor
13604 * @param {NGHammer} manager
13605 * @returns {Input}
13606 */
13607function createInputInstance(manager) {
13608 var Type;
13609 var inputClass = manager.options.inputClass;
13610
13611 if (inputClass) {
13612 Type = inputClass;
13613 } else if (SUPPORT_POINTER_EVENTS) {
13614 Type = PointerEventInput;
13615 } else if (SUPPORT_ONLY_TOUCH) {
13616 Type = TouchInput;
13617 } else if (!SUPPORT_TOUCH) {
13618 Type = MouseInput;
13619 } else {
13620 Type = TouchMouseInput;
13621 }
13622 return new (Type)(manager, inputHandler);
13623}
13624
13625/**
13626 * handle input events
13627 * @param {Manager} manager
13628 * @param {String} eventType
13629 * @param {Object} input
13630 */
13631function inputHandler(manager, eventType, input) {
13632 var pointersLen = input.pointers.length;
13633 var changedPointersLen = input.changedPointers.length;
13634 var isFirst = (eventType & INPUT_START && (pointersLen - changedPointersLen === 0));
13635 var isFinal = (eventType & (INPUT_END | INPUT_CANCEL) && (pointersLen - changedPointersLen === 0));
13636
13637 input.isFirst = !!isFirst;
13638 input.isFinal = !!isFinal;
13639
13640 if (isFirst) {
13641 manager.session = {};
13642 }
13643
13644 // source event is the normalized value of the domEvents
13645 // like 'touchstart, mouseup, pointerdown'
13646 input.eventType = eventType;
13647
13648 // compute scale, rotation etc
13649 computeInputData(manager, input);
13650
13651 // emit secret event
13652 manager.emit('hammer.input', input);
13653
13654 manager.recognize(input);
13655 manager.session.prevInput = input;
13656}
13657
13658/**
13659 * extend the data with some usable properties like scale, rotate, velocity etc
13660 * @param {Object} manager
13661 * @param {Object} input
13662 */
13663function computeInputData(manager, input) {
13664 var session = manager.session;
13665 var pointers = input.pointers;
13666 var pointersLength = pointers.length;
13667
13668 // store the first input to calculate the distance and direction
13669 if (!session.firstInput) {
13670 session.firstInput = simpleCloneInputData(input);
13671 }
13672
13673 // to compute scale and rotation we need to store the multiple touches
13674 if (pointersLength > 1 && !session.firstMultiple) {
13675 session.firstMultiple = simpleCloneInputData(input);
13676 } else if (pointersLength === 1) {
13677 session.firstMultiple = false;
13678 }
13679
13680 var firstInput = session.firstInput;
13681 var firstMultiple = session.firstMultiple;
13682 var offsetCenter = firstMultiple ? firstMultiple.center : firstInput.center;
13683
13684 var center = input.center = getCenter(pointers);
13685 input.timeStamp = now();
13686 input.deltaTime = input.timeStamp - firstInput.timeStamp;
13687
13688 input.angle = getAngle(offsetCenter, center);
13689 input.distance = getDistance(offsetCenter, center);
13690
13691 computeDeltaXY(session, input);
13692 input.offsetDirection = getDirection(input.deltaX, input.deltaY);
13693
13694 var overallVelocity = getVelocity(input.deltaTime, input.deltaX, input.deltaY);
13695 input.overallVelocityX = overallVelocity.x;
13696 input.overallVelocityY = overallVelocity.y;
13697 input.overallVelocity = (abs(overallVelocity.x) > abs(overallVelocity.y)) ? overallVelocity.x : overallVelocity.y;
13698
13699 input.scale = firstMultiple ? getScale(firstMultiple.pointers, pointers) : 1;
13700 input.rotation = firstMultiple ? getRotation(firstMultiple.pointers, pointers) : 0;
13701
13702 input.maxPointers = !session.prevInput ? input.pointers.length : ((input.pointers.length >
13703 session.prevInput.maxPointers) ? input.pointers.length : session.prevInput.maxPointers);
13704
13705 computeIntervalInputData(session, input);
13706
13707 // find the correct target
13708 var target = manager.element;
13709 if (hasParent(input.srcEvent.target, target)) {
13710 target = input.srcEvent.target;
13711 }
13712 input.target = target;
13713}
13714
13715function computeDeltaXY(session, input) {
13716 var center = input.center;
13717 var offset = session.offsetDelta || {};
13718 var prevDelta = session.prevDelta || {};
13719 var prevInput = session.prevInput || {};
13720
13721 if (input.eventType === INPUT_START || prevInput.eventType === INPUT_END) {
13722 prevDelta = session.prevDelta = {
13723 x: prevInput.deltaX || 0,
13724 y: prevInput.deltaY || 0
13725 };
13726
13727 offset = session.offsetDelta = {
13728 x: center.x,
13729 y: center.y
13730 };
13731 }
13732
13733 input.deltaX = prevDelta.x + (center.x - offset.x);
13734 input.deltaY = prevDelta.y + (center.y - offset.y);
13735}
13736
13737/**
13738 * velocity is calculated every x ms
13739 * @param {Object} session
13740 * @param {Object} input
13741 */
13742function computeIntervalInputData(session, input) {
13743 var last = session.lastInterval || input,
13744 deltaTime = input.timeStamp - last.timeStamp,
13745 velocity, velocityX, velocityY, direction;
13746
13747 if (input.eventType != INPUT_CANCEL && (deltaTime > COMPUTE_INTERVAL || last.velocity === undefined)) {
13748 var deltaX = input.deltaX - last.deltaX;
13749 var deltaY = input.deltaY - last.deltaY;
13750
13751 var v = getVelocity(deltaTime, deltaX, deltaY);
13752 velocityX = v.x;
13753 velocityY = v.y;
13754 velocity = (abs(v.x) > abs(v.y)) ? v.x : v.y;
13755 direction = getDirection(deltaX, deltaY);
13756
13757 session.lastInterval = input;
13758 } else {
13759 // use latest velocity info if it doesn't overtake a minimum period
13760 velocity = last.velocity;
13761 velocityX = last.velocityX;
13762 velocityY = last.velocityY;
13763 direction = last.direction;
13764 }
13765
13766 input.velocity = velocity;
13767 input.velocityX = velocityX;
13768 input.velocityY = velocityY;
13769 input.direction = direction;
13770}
13771
13772/**
13773 * create a simple clone from the input used for storage of firstInput and firstMultiple
13774 * @param {Object} input
13775 * @returns {Object} clonedInputData
13776 */
13777function simpleCloneInputData(input) {
13778 // make a simple copy of the pointers because we will get a reference if we don't
13779 // we only need clientXY for the calculations
13780 var pointers = [];
13781 var i = 0;
13782 while (i < input.pointers.length) {
13783 pointers[i] = {
13784 clientX: round(input.pointers[i].clientX),
13785 clientY: round(input.pointers[i].clientY)
13786 };
13787 i++;
13788 }
13789
13790 return {
13791 timeStamp: now(),
13792 pointers: pointers,
13793 center: getCenter(pointers),
13794 deltaX: input.deltaX,
13795 deltaY: input.deltaY
13796 };
13797}
13798
13799/**
13800 * get the center of all the pointers
13801 * @param {Array} pointers
13802 * @return {Object} center contains `x` and `y` properties
13803 */
13804function getCenter(pointers) {
13805 var pointersLength = pointers.length;
13806
13807 // no need to loop when only one touch
13808 if (pointersLength === 1) {
13809 return {
13810 x: round(pointers[0].clientX),
13811 y: round(pointers[0].clientY)
13812 };
13813 }
13814
13815 var x = 0, y = 0, i = 0;
13816 while (i < pointersLength) {
13817 x += pointers[i].clientX;
13818 y += pointers[i].clientY;
13819 i++;
13820 }
13821
13822 return {
13823 x: round(x / pointersLength),
13824 y: round(y / pointersLength)
13825 };
13826}
13827
13828/**
13829 * calculate the velocity between two points. unit is in px per ms.
13830 * @param {Number} deltaTime
13831 * @param {Number} x
13832 * @param {Number} y
13833 * @return {Object} velocity `x` and `y`
13834 */
13835function getVelocity(deltaTime, x, y) {
13836 return {
13837 x: x / deltaTime || 0,
13838 y: y / deltaTime || 0
13839 };
13840}
13841
13842/**
13843 * get the direction between two points
13844 * @param {Number} x
13845 * @param {Number} y
13846 * @return {Number} direction
13847 */
13848function getDirection(x, y) {
13849 if (x === y) {
13850 return DIRECTION_NONE;
13851 }
13852
13853 if (abs(x) >= abs(y)) {
13854 return x < 0 ? DIRECTION_LEFT : DIRECTION_RIGHT;
13855 }
13856 return y < 0 ? DIRECTION_UP : DIRECTION_DOWN;
13857}
13858
13859/**
13860 * calculate the absolute distance between two points
13861 * @param {Object} p1 {x, y}
13862 * @param {Object} p2 {x, y}
13863 * @param {Array} [props] containing x and y keys
13864 * @return {Number} distance
13865 */
13866function getDistance(p1, p2, props) {
13867 if (!props) {
13868 props = PROPS_XY;
13869 }
13870 var x = p2[props[0]] - p1[props[0]],
13871 y = p2[props[1]] - p1[props[1]];
13872
13873 return Math.sqrt((x * x) + (y * y));
13874}
13875
13876/**
13877 * calculate the angle between two coordinates
13878 * @param {Object} p1
13879 * @param {Object} p2
13880 * @param {Array} [props] containing x and y keys
13881 * @return {Number} angle
13882 */
13883function getAngle(p1, p2, props) {
13884 if (!props) {
13885 props = PROPS_XY;
13886 }
13887 var x = p2[props[0]] - p1[props[0]],
13888 y = p2[props[1]] - p1[props[1]];
13889 return Math.atan2(y, x) * 180 / Math.PI;
13890}
13891
13892/**
13893 * calculate the rotation degrees between two pointersets
13894 * @param {Array} start array of pointers
13895 * @param {Array} end array of pointers
13896 * @return {Number} rotation
13897 */
13898function getRotation(start, end) {
13899 return getAngle(end[1], end[0], PROPS_CLIENT_XY) + getAngle(start[1], start[0], PROPS_CLIENT_XY);
13900}
13901
13902/**
13903 * calculate the scale factor between two pointersets
13904 * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out
13905 * @param {Array} start array of pointers
13906 * @param {Array} end array of pointers
13907 * @return {Number} scale
13908 */
13909function getScale(start, end) {
13910 return getDistance(end[0], end[1], PROPS_CLIENT_XY) / getDistance(start[0], start[1], PROPS_CLIENT_XY);
13911}
13912
13913var MOUSE_INPUT_MAP = {
13914 mousedown: INPUT_START,
13915 mousemove: INPUT_MOVE,
13916 mouseup: INPUT_END
13917};
13918
13919var MOUSE_ELEMENT_EVENTS = 'mousedown';
13920var MOUSE_WINDOW_EVENTS = 'mousemove mouseup';
13921
13922/**
13923 * Mouse events input
13924 * @constructor
13925 * @extends Input
13926 */
13927function MouseInput() {
13928 this.evEl = MOUSE_ELEMENT_EVENTS;
13929 this.evWin = MOUSE_WINDOW_EVENTS;
13930
13931 this.pressed = false; // mousedown state
13932
13933 Input.apply(this, arguments);
13934}
13935
13936inherit(MouseInput, Input, {
13937 /**
13938 * handle mouse events
13939 * @param {Object} ev
13940 */
13941 handler: function MEhandler(ev) {
13942 var eventType = MOUSE_INPUT_MAP[ev.type];
13943
13944 // on start we want to have the left mouse button down
13945 if (eventType & INPUT_START && ev.button === 0) {
13946 this.pressed = true;
13947 }
13948
13949 if (eventType & INPUT_MOVE && ev.which !== 1) {
13950 eventType = INPUT_END;
13951 }
13952
13953 // mouse must be down
13954 if (!this.pressed) {
13955 return;
13956 }
13957
13958 if (eventType & INPUT_END) {
13959 this.pressed = false;
13960 }
13961
13962 this.callback(this.manager, eventType, {
13963 pointers: [ev],
13964 changedPointers: [ev],
13965 pointerType: INPUT_TYPE_MOUSE,
13966 srcEvent: ev
13967 });
13968 }
13969});
13970
13971var POINTER_INPUT_MAP = {
13972 pointerdown: INPUT_START,
13973 pointermove: INPUT_MOVE,
13974 pointerup: INPUT_END,
13975 pointercancel: INPUT_CANCEL,
13976 pointerout: INPUT_CANCEL
13977};
13978
13979// in IE10 the pointer types is defined as an enum
13980var IE10_POINTER_TYPE_ENUM = {
13981 2: INPUT_TYPE_TOUCH,
13982 3: INPUT_TYPE_PEN,
13983 4: INPUT_TYPE_MOUSE,
13984 5: INPUT_TYPE_KINECT // see https://twitter.com/jacobrossi/status/480596438489890816
13985};
13986
13987var POINTER_ELEMENT_EVENTS = 'pointerdown';
13988var POINTER_WINDOW_EVENTS = 'pointermove pointerup pointercancel';
13989
13990// IE10 has prefixed support, and case-sensitive
13991if (window.MSPointerEvent && !window.PointerEvent) {
13992 POINTER_ELEMENT_EVENTS = 'MSPointerDown';
13993 POINTER_WINDOW_EVENTS = 'MSPointerMove MSPointerUp MSPointerCancel';
13994}
13995
13996/**
13997 * Pointer events input
13998 * @constructor
13999 * @extends Input
14000 */
14001function PointerEventInput() {
14002 this.evEl = POINTER_ELEMENT_EVENTS;
14003 this.evWin = POINTER_WINDOW_EVENTS;
14004
14005 Input.apply(this, arguments);
14006
14007 this.store = (this.manager.session.pointerEvents = []);
14008}
14009
14010inherit(PointerEventInput, Input, {
14011 /**
14012 * handle mouse events
14013 * @param {Object} ev
14014 */
14015 handler: function PEhandler(ev) {
14016 var store = this.store;
14017 var removePointer = false;
14018
14019 var eventTypeNormalized = ev.type.toLowerCase().replace('ms', '');
14020 var eventType = POINTER_INPUT_MAP[eventTypeNormalized];
14021 var pointerType = IE10_POINTER_TYPE_ENUM[ev.pointerType] || ev.pointerType;
14022
14023 var isTouch = (pointerType == INPUT_TYPE_TOUCH);
14024
14025 // get index of the event in the store
14026 var storeIndex = inArray(store, ev.pointerId, 'pointerId');
14027
14028 // start and mouse must be down
14029 if (eventType & INPUT_START && (ev.button === 0 || isTouch)) {
14030 if (storeIndex < 0) {
14031 store.push(ev);
14032 storeIndex = store.length - 1;
14033 }
14034 } else if (eventType & (INPUT_END | INPUT_CANCEL)) {
14035 removePointer = true;
14036 }
14037
14038 // it not found, so the pointer hasn't been down (so it's probably a hover)
14039 if (storeIndex < 0) {
14040 return;
14041 }
14042
14043 // update the event in the store
14044 store[storeIndex] = ev;
14045
14046 this.callback(this.manager, eventType, {
14047 pointers: store,
14048 changedPointers: [ev],
14049 pointerType: pointerType,
14050 srcEvent: ev
14051 });
14052
14053 if (removePointer) {
14054 // remove from the store
14055 store.splice(storeIndex, 1);
14056 }
14057 }
14058});
14059
14060var SINGLE_TOUCH_INPUT_MAP = {
14061 touchstart: INPUT_START,
14062 touchmove: INPUT_MOVE,
14063 touchend: INPUT_END,
14064 touchcancel: INPUT_CANCEL
14065};
14066
14067var SINGLE_TOUCH_TARGET_EVENTS = 'touchstart';
14068var SINGLE_TOUCH_WINDOW_EVENTS = 'touchstart touchmove touchend touchcancel';
14069
14070/**
14071 * Touch events input
14072 * @constructor
14073 * @extends Input
14074 */
14075function SingleTouchInput() {
14076 this.evTarget = SINGLE_TOUCH_TARGET_EVENTS;
14077 this.evWin = SINGLE_TOUCH_WINDOW_EVENTS;
14078 this.started = false;
14079
14080 Input.apply(this, arguments);
14081}
14082
14083inherit(SingleTouchInput, Input, {
14084 handler: function TEhandler(ev) {
14085 var type = SINGLE_TOUCH_INPUT_MAP[ev.type];
14086
14087 // should we handle the touch events?
14088 if (type === INPUT_START) {
14089 this.started = true;
14090 }
14091
14092 if (!this.started) {
14093 return;
14094 }
14095
14096 var touches = normalizeSingleTouches.call(this, ev, type);
14097
14098 // when done, reset the started state
14099 if (type & (INPUT_END | INPUT_CANCEL) && touches[0].length - touches[1].length === 0) {
14100 this.started = false;
14101 }
14102
14103 this.callback(this.manager, type, {
14104 pointers: touches[0],
14105 changedPointers: touches[1],
14106 pointerType: INPUT_TYPE_TOUCH,
14107 srcEvent: ev
14108 });
14109 }
14110});
14111
14112/**
14113 * @this {TouchInput}
14114 * @param {Object} ev
14115 * @param {Number} type flag
14116 * @returns {undefined|Array} [all, changed]
14117 */
14118function normalizeSingleTouches(ev, type) {
14119 var all = toArray(ev.touches);
14120 var changed = toArray(ev.changedTouches);
14121
14122 if (type & (INPUT_END | INPUT_CANCEL)) {
14123 all = uniqueArray(all.concat(changed), 'identifier', true);
14124 }
14125
14126 return [all, changed];
14127}
14128
14129var TOUCH_INPUT_MAP = {
14130 touchstart: INPUT_START,
14131 touchmove: INPUT_MOVE,
14132 touchend: INPUT_END,
14133 touchcancel: INPUT_CANCEL
14134};
14135
14136var TOUCH_TARGET_EVENTS = 'touchstart touchmove touchend touchcancel';
14137
14138/**
14139 * Multi-user touch events input
14140 * @constructor
14141 * @extends Input
14142 */
14143function TouchInput() {
14144 this.evTarget = TOUCH_TARGET_EVENTS;
14145 this.targetIds = {};
14146
14147 Input.apply(this, arguments);
14148}
14149
14150inherit(TouchInput, Input, {
14151 handler: function MTEhandler(ev) {
14152 var type = TOUCH_INPUT_MAP[ev.type];
14153 var touches = getTouches.call(this, ev, type);
14154 if (!touches) {
14155 return;
14156 }
14157
14158 this.callback(this.manager, type, {
14159 pointers: touches[0],
14160 changedPointers: touches[1],
14161 pointerType: INPUT_TYPE_TOUCH,
14162 srcEvent: ev
14163 });
14164 }
14165});
14166
14167/**
14168 * @this {TouchInput}
14169 * @param {Object} ev
14170 * @param {Number} type flag
14171 * @returns {undefined|Array} [all, changed]
14172 */
14173function getTouches(ev, type) {
14174 var allTouches = toArray(ev.touches);
14175 var targetIds = this.targetIds;
14176
14177 // when there is only one touch, the process can be simplified
14178 if (type & (INPUT_START | INPUT_MOVE) && allTouches.length === 1) {
14179 targetIds[allTouches[0].identifier] = true;
14180 return [allTouches, allTouches];
14181 }
14182
14183 var i,
14184 targetTouches,
14185 changedTouches = toArray(ev.changedTouches),
14186 changedTargetTouches = [],
14187 target = this.target;
14188
14189 // get target touches from touches
14190 targetTouches = allTouches.filter(function(touch) {
14191 return hasParent(touch.target, target);
14192 });
14193
14194 // collect touches
14195 if (type === INPUT_START) {
14196 i = 0;
14197 while (i < targetTouches.length) {
14198 targetIds[targetTouches[i].identifier] = true;
14199 i++;
14200 }
14201 }
14202
14203 // filter changed touches to only contain touches that exist in the collected target ids
14204 i = 0;
14205 while (i < changedTouches.length) {
14206 if (targetIds[changedTouches[i].identifier]) {
14207 changedTargetTouches.push(changedTouches[i]);
14208 }
14209
14210 // cleanup removed touches
14211 if (type & (INPUT_END | INPUT_CANCEL)) {
14212 delete targetIds[changedTouches[i].identifier];
14213 }
14214 i++;
14215 }
14216
14217 if (!changedTargetTouches.length) {
14218 return;
14219 }
14220
14221 return [
14222 // merge targetTouches with changedTargetTouches so it contains ALL touches, including 'end' and 'cancel'
14223 uniqueArray(targetTouches.concat(changedTargetTouches), 'identifier', true),
14224 changedTargetTouches
14225 ];
14226}
14227
14228/**
14229 * Combined touch and mouse input
14230 *
14231 * Touch has a higher priority then mouse, and while touching no mouse events are allowed.
14232 * This because touch devices also emit mouse events while doing a touch.
14233 *
14234 * @constructor
14235 * @extends Input
14236 */
14237
14238var DEDUP_TIMEOUT = 2500;
14239var DEDUP_DISTANCE = 25;
14240
14241function TouchMouseInput() {
14242 Input.apply(this, arguments);
14243
14244 var handler = bindFn(this.handler, this);
14245 this.touch = new TouchInput(this.manager, handler);
14246 this.mouse = new MouseInput(this.manager, handler);
14247
14248 this.primaryTouch = null;
14249 this.lastTouches = [];
14250}
14251
14252inherit(TouchMouseInput, Input, {
14253 /**
14254 * handle mouse and touch events
14255 * @param {NGHammer} manager
14256 * @param {String} inputEvent
14257 * @param {Object} inputData
14258 */
14259 handler: function TMEhandler(manager, inputEvent, inputData) {
14260 var isTouch = (inputData.pointerType == INPUT_TYPE_TOUCH),
14261 isMouse = (inputData.pointerType == INPUT_TYPE_MOUSE);
14262
14263 if (isMouse && inputData.sourceCapabilities && inputData.sourceCapabilities.firesTouchEvents) {
14264 return;
14265 }
14266
14267 // when we're in a touch event, record touches to de-dupe synthetic mouse event
14268 if (isTouch) {
14269 recordTouches.call(this, inputEvent, inputData);
14270 } else if (isMouse && isSyntheticEvent.call(this, inputData)) {
14271 return;
14272 }
14273
14274 this.callback(manager, inputEvent, inputData);
14275 },
14276
14277 /**
14278 * remove the event listeners
14279 */
14280 destroy: function destroy() {
14281 this.touch.destroy();
14282 this.mouse.destroy();
14283 }
14284});
14285
14286function recordTouches(eventType, eventData) {
14287 if (eventType & INPUT_START) {
14288 this.primaryTouch = eventData.changedPointers[0].identifier;
14289 setLastTouch.call(this, eventData);
14290 } else if (eventType & (INPUT_END | INPUT_CANCEL)) {
14291 setLastTouch.call(this, eventData);
14292 }
14293}
14294
14295function setLastTouch(eventData) {
14296 var touch = eventData.changedPointers[0];
14297
14298 if (touch.identifier === this.primaryTouch) {
14299 var lastTouch = {x: touch.clientX, y: touch.clientY};
14300 this.lastTouches.push(lastTouch);
14301 var lts = this.lastTouches;
14302 var removeLastTouch = function() {
14303 var i = lts.indexOf(lastTouch);
14304 if (i > -1) {
14305 lts.splice(i, 1);
14306 }
14307 };
14308 setTimeout(removeLastTouch, DEDUP_TIMEOUT);
14309 }
14310}
14311
14312function isSyntheticEvent(eventData) {
14313 var x = eventData.srcEvent.clientX, y = eventData.srcEvent.clientY;
14314 for (var i = 0; i < this.lastTouches.length; i++) {
14315 var t = this.lastTouches[i];
14316 var dx = Math.abs(x - t.x), dy = Math.abs(y - t.y);
14317 if (dx <= DEDUP_DISTANCE && dy <= DEDUP_DISTANCE) {
14318 return true;
14319 }
14320 }
14321 return false;
14322}
14323
14324var PREFIXED_TOUCH_ACTION = prefixed(TEST_ELEMENT.style, 'touchAction');
14325var NATIVE_TOUCH_ACTION = PREFIXED_TOUCH_ACTION !== undefined;
14326
14327// magical touchAction value
14328var TOUCH_ACTION_COMPUTE = 'compute';
14329var TOUCH_ACTION_AUTO = 'auto';
14330var TOUCH_ACTION_MANIPULATION = 'manipulation'; // not implemented
14331var TOUCH_ACTION_NONE = 'none';
14332var TOUCH_ACTION_PAN_X = 'pan-x';
14333var TOUCH_ACTION_PAN_Y = 'pan-y';
14334var TOUCH_ACTION_MAP = getTouchActionProps();
14335
14336/**
14337 * Touch Action
14338 * sets the touchAction property or uses the js alternative
14339 * @param {Manager} manager
14340 * @param {String} value
14341 * @constructor
14342 */
14343function TouchAction(manager, value) {
14344 this.manager = manager;
14345 this.set(value);
14346}
14347
14348TouchAction.prototype = {
14349 /**
14350 * set the touchAction value on the element or enable the polyfill
14351 * @param {String} value
14352 */
14353 set: function(value) {
14354 // find out the touch-action by the event handlers
14355 if (value == TOUCH_ACTION_COMPUTE) {
14356 value = this.compute();
14357 }
14358
14359 if (NATIVE_TOUCH_ACTION && this.manager.element.style && TOUCH_ACTION_MAP[value]) {
14360 this.manager.element.style[PREFIXED_TOUCH_ACTION] = value;
14361 }
14362 this.actions = value.toLowerCase().trim();
14363 },
14364
14365 /**
14366 * just re-set the touchAction value
14367 */
14368 update: function() {
14369 this.set(this.manager.options.touchAction);
14370 },
14371
14372 /**
14373 * compute the value for the touchAction property based on the recognizer's settings
14374 * @returns {String} value
14375 */
14376 compute: function() {
14377 var actions = [];
14378 each(this.manager.recognizers, function(recognizer) {
14379 if (boolOrFn(recognizer.options.enable, [recognizer])) {
14380 actions = actions.concat(recognizer.getTouchAction());
14381 }
14382 });
14383 return cleanTouchActions(actions.join(' '));
14384 },
14385
14386 /**
14387 * this method is called on each input cycle and provides the preventing of the browser behavior
14388 * @param {Object} input
14389 */
14390 preventDefaults: function(input) {
14391 var srcEvent = input.srcEvent;
14392 var direction = input.offsetDirection;
14393
14394 // if the touch action did prevented once this session
14395 if (this.manager.session.prevented) {
14396 srcEvent.preventDefault();
14397 return;
14398 }
14399
14400 var actions = this.actions;
14401 var hasNone = inStr(actions, TOUCH_ACTION_NONE) && !TOUCH_ACTION_MAP[TOUCH_ACTION_NONE];
14402 var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_Y];
14403 var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_X];
14404
14405 if (hasNone) {
14406 //do not prevent defaults if this is a tap gesture
14407
14408 var isTapPointer = input.pointers.length === 1;
14409 var isTapMovement = input.distance < 2;
14410 var isTapTouchTime = input.deltaTime < 250;
14411
14412 if (isTapPointer && isTapMovement && isTapTouchTime) {
14413 return;
14414 }
14415 }
14416
14417 if (hasPanX && hasPanY) {
14418 // `pan-x pan-y` means browser handles all scrolling/panning, do not prevent
14419 return;
14420 }
14421
14422 if (hasNone ||
14423 (hasPanY && direction & DIRECTION_HORIZONTAL) ||
14424 (hasPanX && direction & DIRECTION_VERTICAL)) {
14425 return this.preventSrc(srcEvent);
14426 }
14427 },
14428
14429 /**
14430 * call preventDefault to prevent the browser's default behavior (scrolling in most cases)
14431 * @param {Object} srcEvent
14432 */
14433 preventSrc: function(srcEvent) {
14434 this.manager.session.prevented = true;
14435 srcEvent.preventDefault();
14436 }
14437};
14438
14439/**
14440 * when the touchActions are collected they are not a valid value, so we need to clean things up. *
14441 * @param {String} actions
14442 * @returns {*}
14443 */
14444function cleanTouchActions(actions) {
14445 // none
14446 if (inStr(actions, TOUCH_ACTION_NONE)) {
14447 return TOUCH_ACTION_NONE;
14448 }
14449
14450 var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X);
14451 var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y);
14452
14453 // if both pan-x and pan-y are set (different recognizers
14454 // for different directions, e.g. horizontal pan but vertical swipe?)
14455 // we need none (as otherwise with pan-x pan-y combined none of these
14456 // recognizers will work, since the browser would handle all panning
14457 if (hasPanX && hasPanY) {
14458 return TOUCH_ACTION_NONE;
14459 }
14460
14461 // pan-x OR pan-y
14462 if (hasPanX || hasPanY) {
14463 return hasPanX ? TOUCH_ACTION_PAN_X : TOUCH_ACTION_PAN_Y;
14464 }
14465
14466 // manipulation
14467 if (inStr(actions, TOUCH_ACTION_MANIPULATION)) {
14468 return TOUCH_ACTION_MANIPULATION;
14469 }
14470
14471 return TOUCH_ACTION_AUTO;
14472}
14473
14474function getTouchActionProps() {
14475 if (!NATIVE_TOUCH_ACTION) {
14476 return false;
14477 }
14478 var touchMap = {};
14479 var cssSupports = window.CSS && window.CSS.supports;
14480 ['auto', 'manipulation', 'pan-y', 'pan-x', 'pan-x pan-y', 'none'].forEach(function(val) {
14481
14482 // If css.supports is not supported but there is native touch-action assume it supports
14483 // all values. This is the case for IE 10 and 11.
14484 touchMap[val] = cssSupports ? window.CSS.supports('touch-action', val) : true;
14485 });
14486 return touchMap;
14487}
14488
14489/**
14490 * Recognizer flow explained; *
14491 * All recognizers have the initial state of POSSIBLE when a input session starts.
14492 * The definition of a input session is from the first input until the last input, with all it's movement in it. *
14493 * Example session for mouse-input: mousedown -> mousemove -> mouseup
14494 *
14495 * On each recognizing cycle (see Manager.recognize) the .recognize() method is executed
14496 * which determines with state it should be.
14497 *
14498 * If the recognizer has the state FAILED, CANCELLED or RECOGNIZED (equals ENDED), it is reset to
14499 * POSSIBLE to give it another change on the next cycle.
14500 *
14501 * Possible
14502 * |
14503 * +-----+---------------+
14504 * | |
14505 * +-----+-----+ |
14506 * | | |
14507 * Failed Cancelled |
14508 * +-------+------+
14509 * | |
14510 * Recognized Began
14511 * |
14512 * Changed
14513 * |
14514 * Ended/Recognized
14515 */
14516var STATE_POSSIBLE = 1;
14517var STATE_BEGAN = 2;
14518var STATE_CHANGED = 4;
14519var STATE_ENDED = 8;
14520var STATE_RECOGNIZED = STATE_ENDED;
14521var STATE_CANCELLED = 16;
14522var STATE_FAILED = 32;
14523
14524/**
14525 * Recognizer
14526 * Every recognizer needs to extend from this class.
14527 * @constructor
14528 * @param {Object} options
14529 */
14530function Recognizer(options) {
14531 this.options = assign({}, this.defaults, options || {});
14532
14533 this.id = uniqueId();
14534
14535 this.manager = null;
14536
14537 // default is enable true
14538 this.options.enable = ifUndefined(this.options.enable, true);
14539
14540 this.state = STATE_POSSIBLE;
14541
14542 this.simultaneous = {};
14543 this.requireFail = [];
14544}
14545
14546Recognizer.prototype = {
14547 /**
14548 * @virtual
14549 * @type {Object}
14550 */
14551 defaults: {},
14552
14553 /**
14554 * set options
14555 * @param {Object} options
14556 * @return {Recognizer}
14557 */
14558 set: function(options) {
14559 assign(this.options, options);
14560
14561 // also update the touchAction, in case something changed about the directions/enabled state
14562 this.manager && this.manager.touchAction.update();
14563 return this;
14564 },
14565
14566 /**
14567 * recognize simultaneous with an other recognizer.
14568 * @param {Recognizer} otherRecognizer
14569 * @returns {Recognizer} this
14570 */
14571 recognizeWith: function(otherRecognizer) {
14572 if (invokeArrayArg(otherRecognizer, 'recognizeWith', this)) {
14573 return this;
14574 }
14575
14576 var simultaneous = this.simultaneous;
14577 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
14578 if (!simultaneous[otherRecognizer.id]) {
14579 simultaneous[otherRecognizer.id] = otherRecognizer;
14580 otherRecognizer.recognizeWith(this);
14581 }
14582 return this;
14583 },
14584
14585 /**
14586 * drop the simultaneous link. it doesnt remove the link on the other recognizer.
14587 * @param {Recognizer} otherRecognizer
14588 * @returns {Recognizer} this
14589 */
14590 dropRecognizeWith: function(otherRecognizer) {
14591 if (invokeArrayArg(otherRecognizer, 'dropRecognizeWith', this)) {
14592 return this;
14593 }
14594
14595 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
14596 delete this.simultaneous[otherRecognizer.id];
14597 return this;
14598 },
14599
14600 /**
14601 * recognizer can only run when an other is failing
14602 * @param {Recognizer} otherRecognizer
14603 * @returns {Recognizer} this
14604 */
14605 requireFailure: function(otherRecognizer) {
14606 if (invokeArrayArg(otherRecognizer, 'requireFailure', this)) {
14607 return this;
14608 }
14609
14610 var requireFail = this.requireFail;
14611 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
14612 if (inArray(requireFail, otherRecognizer) === -1) {
14613 requireFail.push(otherRecognizer);
14614 otherRecognizer.requireFailure(this);
14615 }
14616 return this;
14617 },
14618
14619 /**
14620 * drop the requireFailure link. it does not remove the link on the other recognizer.
14621 * @param {Recognizer} otherRecognizer
14622 * @returns {Recognizer} this
14623 */
14624 dropRequireFailure: function(otherRecognizer) {
14625 if (invokeArrayArg(otherRecognizer, 'dropRequireFailure', this)) {
14626 return this;
14627 }
14628
14629 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
14630 var index = inArray(this.requireFail, otherRecognizer);
14631 if (index > -1) {
14632 this.requireFail.splice(index, 1);
14633 }
14634 return this;
14635 },
14636
14637 /**
14638 * has require failures boolean
14639 * @returns {boolean}
14640 */
14641 hasRequireFailures: function() {
14642 return this.requireFail.length > 0;
14643 },
14644
14645 /**
14646 * if the recognizer can recognize simultaneous with an other recognizer
14647 * @param {Recognizer} otherRecognizer
14648 * @returns {Boolean}
14649 */
14650 canRecognizeWith: function(otherRecognizer) {
14651 return !!this.simultaneous[otherRecognizer.id];
14652 },
14653
14654 /**
14655 * You should use `tryEmit` instead of `emit` directly to check
14656 * that all the needed recognizers has failed before emitting.
14657 * @param {Object} input
14658 */
14659 emit: function(input) {
14660 var self = this;
14661 var state = this.state;
14662
14663 function emit(event) {
14664 self.manager.emit(event, input);
14665 }
14666
14667 // 'panstart' and 'panmove'
14668 if (state < STATE_ENDED) {
14669 emit(self.options.event + stateStr(state));
14670 }
14671
14672 emit(self.options.event); // simple 'eventName' events
14673
14674 if (input.additionalEvent) { // additional event(panleft, panright, pinchin, pinchout...)
14675 emit(input.additionalEvent);
14676 }
14677
14678 // panend and pancancel
14679 if (state >= STATE_ENDED) {
14680 emit(self.options.event + stateStr(state));
14681 }
14682 },
14683
14684 /**
14685 * Check that all the require failure recognizers has failed,
14686 * if true, it emits a gesture event,
14687 * otherwise, setup the state to FAILED.
14688 * @param {Object} input
14689 */
14690 tryEmit: function(input) {
14691 if (this.canEmit()) {
14692 return this.emit(input);
14693 }
14694 // it's failing anyway
14695 this.state = STATE_FAILED;
14696 },
14697
14698 /**
14699 * can we emit?
14700 * @returns {boolean}
14701 */
14702 canEmit: function() {
14703 var i = 0;
14704 while (i < this.requireFail.length) {
14705 if (!(this.requireFail[i].state & (STATE_FAILED | STATE_POSSIBLE))) {
14706 return false;
14707 }
14708 i++;
14709 }
14710 return true;
14711 },
14712
14713 /**
14714 * update the recognizer
14715 * @param {Object} inputData
14716 */
14717 recognize: function(inputData) {
14718 // make a new copy of the inputData
14719 // so we can change the inputData without messing up the other recognizers
14720 var inputDataClone = assign({}, inputData);
14721
14722 // is is enabled and allow recognizing?
14723 if (!boolOrFn(this.options.enable, [this, inputDataClone])) {
14724 this.reset();
14725 this.state = STATE_FAILED;
14726 return;
14727 }
14728
14729 // reset when we've reached the end
14730 if (this.state & (STATE_RECOGNIZED | STATE_CANCELLED | STATE_FAILED)) {
14731 this.state = STATE_POSSIBLE;
14732 }
14733
14734 this.state = this.process(inputDataClone);
14735
14736 // the recognizer has recognized a gesture
14737 // so trigger an event
14738 if (this.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED | STATE_CANCELLED)) {
14739 this.tryEmit(inputDataClone);
14740 }
14741 },
14742
14743 /**
14744 * return the state of the recognizer
14745 * the actual recognizing happens in this method
14746 * @virtual
14747 * @param {Object} inputData
14748 * @returns {Const} STATE
14749 */
14750 process: function(inputData) { }, // jshint ignore:line
14751
14752 /**
14753 * return the preferred touch-action
14754 * @virtual
14755 * @returns {Array}
14756 */
14757 getTouchAction: function() { },
14758
14759 /**
14760 * called when the gesture isn't allowed to recognize
14761 * like when another is being recognized or it is disabled
14762 * @virtual
14763 */
14764 reset: function() { }
14765};
14766
14767/**
14768 * get a usable string, used as event postfix
14769 * @param {Const} state
14770 * @returns {String} state
14771 */
14772function stateStr(state) {
14773 if (state & STATE_CANCELLED) {
14774 return 'cancel';
14775 } else if (state & STATE_ENDED) {
14776 return 'end';
14777 } else if (state & STATE_CHANGED) {
14778 return 'move';
14779 } else if (state & STATE_BEGAN) {
14780 return 'start';
14781 }
14782 return '';
14783}
14784
14785/**
14786 * direction cons to string
14787 * @param {Const} direction
14788 * @returns {String}
14789 */
14790function directionStr(direction) {
14791 if (direction == DIRECTION_DOWN) {
14792 return 'down';
14793 } else if (direction == DIRECTION_UP) {
14794 return 'up';
14795 } else if (direction == DIRECTION_LEFT) {
14796 return 'left';
14797 } else if (direction == DIRECTION_RIGHT) {
14798 return 'right';
14799 }
14800 return '';
14801}
14802
14803/**
14804 * get a recognizer by name if it is bound to a manager
14805 * @param {Recognizer|String} otherRecognizer
14806 * @param {Recognizer} recognizer
14807 * @returns {Recognizer}
14808 */
14809function getRecognizerByNameIfManager(otherRecognizer, recognizer) {
14810 var manager = recognizer.manager;
14811 if (manager) {
14812 return manager.get(otherRecognizer);
14813 }
14814 return otherRecognizer;
14815}
14816
14817/**
14818 * This recognizer is just used as a base for the simple attribute recognizers.
14819 * @constructor
14820 * @extends Recognizer
14821 */
14822function AttrRecognizer() {
14823 Recognizer.apply(this, arguments);
14824}
14825
14826inherit(AttrRecognizer, Recognizer, {
14827 /**
14828 * @namespace
14829 * @memberof AttrRecognizer
14830 */
14831 defaults: {
14832 /**
14833 * @type {Number}
14834 * @default 1
14835 */
14836 pointers: 1
14837 },
14838
14839 /**
14840 * Used to check if it the recognizer receives valid input, like input.distance > 10.
14841 * @memberof AttrRecognizer
14842 * @param {Object} input
14843 * @returns {Boolean} recognized
14844 */
14845 attrTest: function(input) {
14846 var optionPointers = this.options.pointers;
14847 return optionPointers === 0 || input.pointers.length === optionPointers;
14848 },
14849
14850 /**
14851 * Process the input and return the state for the recognizer
14852 * @memberof AttrRecognizer
14853 * @param {Object} input
14854 * @returns {*} State
14855 */
14856 process: function(input) {
14857 var state = this.state;
14858 var eventType = input.eventType;
14859
14860 var isRecognized = state & (STATE_BEGAN | STATE_CHANGED);
14861 var isValid = this.attrTest(input);
14862
14863 // on cancel input and we've recognized before, return STATE_CANCELLED
14864 if (isRecognized && (eventType & INPUT_CANCEL || !isValid)) {
14865 return state | STATE_CANCELLED;
14866 } else if (isRecognized || isValid) {
14867 if (eventType & INPUT_END) {
14868 return state | STATE_ENDED;
14869 } else if (!(state & STATE_BEGAN)) {
14870 return STATE_BEGAN;
14871 }
14872 return state | STATE_CHANGED;
14873 }
14874 return STATE_FAILED;
14875 }
14876});
14877
14878/**
14879 * Pan
14880 * Recognized when the pointer is down and moved in the allowed direction.
14881 * @constructor
14882 * @extends AttrRecognizer
14883 */
14884function PanRecognizer() {
14885 AttrRecognizer.apply(this, arguments);
14886
14887 this.pX = null;
14888 this.pY = null;
14889}
14890
14891inherit(PanRecognizer, AttrRecognizer, {
14892 /**
14893 * @namespace
14894 * @memberof PanRecognizer
14895 */
14896 defaults: {
14897 event: 'pan',
14898 threshold: 10,
14899 pointers: 1,
14900 direction: DIRECTION_ALL
14901 },
14902
14903 getTouchAction: function() {
14904 var direction = this.options.direction;
14905 var actions = [];
14906 if (direction & DIRECTION_HORIZONTAL) {
14907 actions.push(TOUCH_ACTION_PAN_Y);
14908 }
14909 if (direction & DIRECTION_VERTICAL) {
14910 actions.push(TOUCH_ACTION_PAN_X);
14911 }
14912 return actions;
14913 },
14914
14915 directionTest: function(input) {
14916 var options = this.options;
14917 var hasMoved = true;
14918 var distance = input.distance;
14919 var direction = input.direction;
14920 var x = input.deltaX;
14921 var y = input.deltaY;
14922
14923 // lock to axis?
14924 if (!(direction & options.direction)) {
14925 if (options.direction & DIRECTION_HORIZONTAL) {
14926 direction = (x === 0) ? DIRECTION_NONE : (x < 0) ? DIRECTION_LEFT : DIRECTION_RIGHT;
14927 hasMoved = x != this.pX;
14928 distance = Math.abs(input.deltaX);
14929 } else {
14930 direction = (y === 0) ? DIRECTION_NONE : (y < 0) ? DIRECTION_UP : DIRECTION_DOWN;
14931 hasMoved = y != this.pY;
14932 distance = Math.abs(input.deltaY);
14933 }
14934 }
14935 input.direction = direction;
14936 return hasMoved && distance > options.threshold && direction & options.direction;
14937 },
14938
14939 attrTest: function(input) {
14940 return AttrRecognizer.prototype.attrTest.call(this, input) &&
14941 (this.state & STATE_BEGAN || (!(this.state & STATE_BEGAN) && this.directionTest(input)));
14942 },
14943
14944 emit: function(input) {
14945
14946 this.pX = input.deltaX;
14947 this.pY = input.deltaY;
14948
14949 var direction = directionStr(input.direction);
14950
14951 if (direction) {
14952 input.additionalEvent = this.options.event + direction;
14953 }
14954 this._super.emit.call(this, input);
14955 }
14956});
14957
14958/**
14959 * Pinch
14960 * Recognized when two or more pointers are moving toward (zoom-in) or away from each other (zoom-out).
14961 * @constructor
14962 * @extends AttrRecognizer
14963 */
14964function PinchRecognizer() {
14965 AttrRecognizer.apply(this, arguments);
14966}
14967
14968inherit(PinchRecognizer, AttrRecognizer, {
14969 /**
14970 * @namespace
14971 * @memberof PinchRecognizer
14972 */
14973 defaults: {
14974 event: 'pinch',
14975 threshold: 0,
14976 pointers: 2
14977 },
14978
14979 getTouchAction: function() {
14980 return [TOUCH_ACTION_NONE];
14981 },
14982
14983 attrTest: function(input) {
14984 return this._super.attrTest.call(this, input) &&
14985 (Math.abs(input.scale - 1) > this.options.threshold || this.state & STATE_BEGAN);
14986 },
14987
14988 emit: function(input) {
14989 if (input.scale !== 1) {
14990 var inOut = input.scale < 1 ? 'in' : 'out';
14991 input.additionalEvent = this.options.event + inOut;
14992 }
14993 this._super.emit.call(this, input);
14994 }
14995});
14996
14997/**
14998 * Press
14999 * Recognized when the pointer is down for x ms without any movement.
15000 * @constructor
15001 * @extends Recognizer
15002 */
15003function PressRecognizer() {
15004 Recognizer.apply(this, arguments);
15005
15006 this._timer = null;
15007 this._input = null;
15008}
15009
15010inherit(PressRecognizer, Recognizer, {
15011 /**
15012 * @namespace
15013 * @memberof PressRecognizer
15014 */
15015 defaults: {
15016 event: 'press',
15017 pointers: 1,
15018 time: 251, // minimal time of the pointer to be pressed
15019 threshold: 9 // a minimal movement is ok, but keep it low
15020 },
15021
15022 getTouchAction: function() {
15023 return [TOUCH_ACTION_AUTO];
15024 },
15025
15026 process: function(input) {
15027 var options = this.options;
15028 var validPointers = input.pointers.length === options.pointers;
15029 var validMovement = input.distance < options.threshold;
15030 var validTime = input.deltaTime > options.time;
15031
15032 this._input = input;
15033
15034 // we only allow little movement
15035 // and we've reached an end event, so a tap is possible
15036 if (!validMovement || !validPointers || (input.eventType & (INPUT_END | INPUT_CANCEL) && !validTime)) {
15037 this.reset();
15038 } else if (input.eventType & INPUT_START) {
15039 this.reset();
15040 this._timer = setTimeoutContext(function() {
15041 this.state = STATE_RECOGNIZED;
15042 this.tryEmit();
15043 }, options.time, this);
15044 } else if (input.eventType & INPUT_END) {
15045 return STATE_RECOGNIZED;
15046 }
15047 return STATE_FAILED;
15048 },
15049
15050 reset: function() {
15051 clearTimeout(this._timer);
15052 },
15053
15054 emit: function(input) {
15055 if (this.state !== STATE_RECOGNIZED) {
15056 return;
15057 }
15058
15059 if (input && (input.eventType & INPUT_END)) {
15060 this.manager.emit(this.options.event + 'up', input);
15061 } else {
15062 this._input.timeStamp = now();
15063 this.manager.emit(this.options.event, this._input);
15064 }
15065 }
15066});
15067
15068/**
15069 * Rotate
15070 * Recognized when two or more pointer are moving in a circular motion.
15071 * @constructor
15072 * @extends AttrRecognizer
15073 */
15074function RotateRecognizer() {
15075 AttrRecognizer.apply(this, arguments);
15076}
15077
15078inherit(RotateRecognizer, AttrRecognizer, {
15079 /**
15080 * @namespace
15081 * @memberof RotateRecognizer
15082 */
15083 defaults: {
15084 event: 'rotate',
15085 threshold: 0,
15086 pointers: 2
15087 },
15088
15089 getTouchAction: function() {
15090 return [TOUCH_ACTION_NONE];
15091 },
15092
15093 attrTest: function(input) {
15094 return this._super.attrTest.call(this, input) &&
15095 (Math.abs(input.rotation) > this.options.threshold || this.state & STATE_BEGAN);
15096 }
15097});
15098
15099/**
15100 * Swipe
15101 * Recognized when the pointer is moving fast (velocity), with enough distance in the allowed direction.
15102 * @constructor
15103 * @extends AttrRecognizer
15104 */
15105function SwipeRecognizer() {
15106 AttrRecognizer.apply(this, arguments);
15107}
15108
15109inherit(SwipeRecognizer, AttrRecognizer, {
15110 /**
15111 * @namespace
15112 * @memberof SwipeRecognizer
15113 */
15114 defaults: {
15115 event: 'swipe',
15116 threshold: 10,
15117 velocity: 0.3,
15118 direction: DIRECTION_HORIZONTAL | DIRECTION_VERTICAL,
15119 pointers: 1
15120 },
15121
15122 getTouchAction: function() {
15123 return PanRecognizer.prototype.getTouchAction.call(this);
15124 },
15125
15126 attrTest: function(input) {
15127 var direction = this.options.direction;
15128 var velocity;
15129
15130 if (direction & (DIRECTION_HORIZONTAL | DIRECTION_VERTICAL)) {
15131 velocity = input.overallVelocity;
15132 } else if (direction & DIRECTION_HORIZONTAL) {
15133 velocity = input.overallVelocityX;
15134 } else if (direction & DIRECTION_VERTICAL) {
15135 velocity = input.overallVelocityY;
15136 }
15137
15138 return this._super.attrTest.call(this, input) &&
15139 direction & input.offsetDirection &&
15140 input.distance > this.options.threshold &&
15141 input.maxPointers == this.options.pointers &&
15142 abs(velocity) > this.options.velocity && input.eventType & INPUT_END;
15143 },
15144
15145 emit: function(input) {
15146 var direction = directionStr(input.offsetDirection);
15147 if (direction) {
15148 this.manager.emit(this.options.event + direction, input);
15149 }
15150
15151 this.manager.emit(this.options.event, input);
15152 }
15153});
15154
15155/**
15156 * A tap is ecognized when the pointer is doing a small tap/click. Multiple taps are recognized if they occur
15157 * between the given interval and position. The delay option can be used to recognize multi-taps without firing
15158 * a single tap.
15159 *
15160 * The eventData from the emitted event contains the property `tapCount`, which contains the amount of
15161 * multi-taps being recognized.
15162 * @constructor
15163 * @extends Recognizer
15164 */
15165function TapRecognizer() {
15166 Recognizer.apply(this, arguments);
15167
15168 // previous time and center,
15169 // used for tap counting
15170 this.pTime = false;
15171 this.pCenter = false;
15172
15173 this._timer = null;
15174 this._input = null;
15175 this.count = 0;
15176}
15177
15178inherit(TapRecognizer, Recognizer, {
15179 /**
15180 * @namespace
15181 * @memberof PinchRecognizer
15182 */
15183 defaults: {
15184 event: 'tap',
15185 pointers: 1,
15186 taps: 1,
15187 interval: 300, // max time between the multi-tap taps
15188 time: 250, // max time of the pointer to be down (like finger on the screen)
15189 threshold: 9, // a minimal movement is ok, but keep it low
15190 posThreshold: 10 // a multi-tap can be a bit off the initial position
15191 },
15192
15193 getTouchAction: function() {
15194 return [TOUCH_ACTION_MANIPULATION];
15195 },
15196
15197 process: function(input) {
15198 var options = this.options;
15199
15200 var validPointers = input.pointers.length === options.pointers;
15201 var validMovement = input.distance < options.threshold;
15202 var validTouchTime = input.deltaTime < options.time;
15203
15204 this.reset();
15205
15206 if ((input.eventType & INPUT_START) && (this.count === 0)) {
15207 return this.failTimeout();
15208 }
15209
15210 // we only allow little movement
15211 // and we've reached an end event, so a tap is possible
15212 if (validMovement && validTouchTime && validPointers) {
15213 if (input.eventType != INPUT_END) {
15214 return this.failTimeout();
15215 }
15216
15217 var validInterval = this.pTime ? (input.timeStamp - this.pTime < options.interval) : true;
15218 var validMultiTap = !this.pCenter || getDistance(this.pCenter, input.center) < options.posThreshold;
15219
15220 this.pTime = input.timeStamp;
15221 this.pCenter = input.center;
15222
15223 if (!validMultiTap || !validInterval) {
15224 this.count = 1;
15225 } else {
15226 this.count += 1;
15227 }
15228
15229 this._input = input;
15230
15231 // if tap count matches we have recognized it,
15232 // else it has began recognizing...
15233 var tapCount = this.count % options.taps;
15234 if (tapCount === 0) {
15235 // no failing requirements, immediately trigger the tap event
15236 // or wait as long as the multitap interval to trigger
15237 if (!this.hasRequireFailures()) {
15238 return STATE_RECOGNIZED;
15239 } else {
15240 this._timer = setTimeoutContext(function() {
15241 this.state = STATE_RECOGNIZED;
15242 this.tryEmit();
15243 }, options.interval, this);
15244 return STATE_BEGAN;
15245 }
15246 }
15247 }
15248 return STATE_FAILED;
15249 },
15250
15251 failTimeout: function() {
15252 this._timer = setTimeoutContext(function() {
15253 this.state = STATE_FAILED;
15254 }, this.options.interval, this);
15255 return STATE_FAILED;
15256 },
15257
15258 reset: function() {
15259 clearTimeout(this._timer);
15260 },
15261
15262 emit: function() {
15263 if (this.state == STATE_RECOGNIZED) {
15264 this._input.tapCount = this.count;
15265 this.manager.emit(this.options.event, this._input);
15266 }
15267 }
15268});
15269
15270/**
15271 * Simple way to create a manager with a default set of recognizers.
15272 * @param {HTMLElement} element
15273 * @param {Object} [options]
15274 * @constructor
15275 */
15276function NGHammer(element, options) {
15277 options = options || {};
15278 options.recognizers = ifUndefined(options.recognizers, NGHammer.defaults.preset);
15279 return new Manager(element, options);
15280}
15281
15282/**
15283 * @const {string}
15284 */
15285NGHammer.VERSION = '2.0.7';
15286
15287/**
15288 * default settings
15289 * @namespace
15290 */
15291NGHammer.defaults = {
15292 /**
15293 * set if DOM events are being triggered.
15294 * But this is slower and unused by simple implementations, so disabled by default.
15295 * @type {Boolean}
15296 * @default false
15297 */
15298 domEvents: false,
15299
15300 /**
15301 * The value for the touchAction property/fallback.
15302 * When set to `compute` it will magically set the correct value based on the added recognizers.
15303 * @type {String}
15304 * @default compute
15305 */
15306 touchAction: TOUCH_ACTION_COMPUTE,
15307
15308 /**
15309 * @type {Boolean}
15310 * @default true
15311 */
15312 enable: true,
15313
15314 /**
15315 * EXPERIMENTAL FEATURE -- can be removed/changed
15316 * Change the parent input target element.
15317 * If Null, then it is being set the to main element.
15318 * @type {Null|EventTarget}
15319 * @default null
15320 */
15321 inputTarget: null,
15322
15323 /**
15324 * force an input class
15325 * @type {Null|Function}
15326 * @default null
15327 */
15328 inputClass: null,
15329
15330 /**
15331 * Default recognizer setup when calling `NGHammer()`
15332 * When creating a new Manager these will be skipped.
15333 * @type {Array}
15334 */
15335 preset: [
15336 // RecognizerClass, options, [recognizeWith, ...], [requireFailure, ...]
15337 [RotateRecognizer, {enable: false}],
15338 [PinchRecognizer, {enable: false}, ['rotate']],
15339 [SwipeRecognizer, {direction: DIRECTION_HORIZONTAL}],
15340 [PanRecognizer, {direction: DIRECTION_HORIZONTAL}, ['swipe']],
15341 [TapRecognizer],
15342 [TapRecognizer, {event: 'doubletap', taps: 2}, ['tap']],
15343 [PressRecognizer]
15344 ],
15345
15346 /**
15347 * Some CSS properties can be used to improve the working of NGHammer.
15348 * Add them to this method and they will be set when creating a new Manager.
15349 * @namespace
15350 */
15351 cssProps: {
15352 /**
15353 * Disables text selection to improve the dragging gesture. Mainly for desktop browsers.
15354 * @type {String}
15355 * @default 'none'
15356 */
15357 userSelect: 'none',
15358
15359 /**
15360 * Disable the Windows Phone grippers when pressing an element.
15361 * @type {String}
15362 * @default 'none'
15363 */
15364 touchSelect: 'none',
15365
15366 /**
15367 * Disables the default callout shown when you touch and hold a touch target.
15368 * On iOS, when you touch and hold a touch target such as a link, Safari displays
15369 * a callout containing information about the link. This property allows you to disable that callout.
15370 * @type {String}
15371 * @default 'none'
15372 */
15373 touchCallout: 'none',
15374
15375 /**
15376 * Specifies whether zooming is enabled. Used by IE10>
15377 * @type {String}
15378 * @default 'none'
15379 */
15380 contentZooming: 'none',
15381
15382 /**
15383 * Specifies that an entire element should be draggable instead of its contents. Mainly for desktop browsers.
15384 * @type {String}
15385 * @default 'none'
15386 */
15387 userDrag: 'none',
15388
15389 /**
15390 * Overrides the highlight color shown when the user taps a link or a JavaScript
15391 * clickable element in iOS. This property obeys the alpha value, if specified.
15392 * @type {String}
15393 * @default 'rgba(0,0,0,0)'
15394 */
15395 tapHighlightColor: 'rgba(0,0,0,0)'
15396 }
15397};
15398
15399var STOP = 1;
15400var FORCED_STOP = 2;
15401
15402/**
15403 * Manager
15404 * @param {HTMLElement} element
15405 * @param {Object} [options]
15406 * @constructor
15407 */
15408function Manager(element, options) {
15409 this.options = assign({}, NGHammer.defaults, options || {});
15410
15411 this.options.inputTarget = this.options.inputTarget || element;
15412
15413 this.handlers = {};
15414 this.session = {};
15415 this.recognizers = [];
15416 this.oldCssProps = {};
15417
15418 this.element = element;
15419 this.input = createInputInstance(this);
15420 this.touchAction = new TouchAction(this, this.options.touchAction);
15421
15422 toggleCssProps(this, true);
15423
15424 each(this.options.recognizers, function(item) {
15425 var recognizer = this.add(new (item[0])(item[1]));
15426 item[2] && recognizer.recognizeWith(item[2]);
15427 item[3] && recognizer.requireFailure(item[3]);
15428 }, this);
15429}
15430
15431Manager.prototype = {
15432 /**
15433 * set options
15434 * @param {Object} options
15435 * @returns {Manager}
15436 */
15437 set: function(options) {
15438 assign(this.options, options);
15439
15440 // Options that need a little more setup
15441 if (options.touchAction) {
15442 this.touchAction.update();
15443 }
15444 if (options.inputTarget) {
15445 // Clean up existing event listeners and reinitialize
15446 this.input.destroy();
15447 this.input.target = options.inputTarget;
15448 this.input.init();
15449 }
15450 return this;
15451 },
15452
15453 /**
15454 * stop recognizing for this session.
15455 * This session will be discarded, when a new [input]start event is fired.
15456 * When forced, the recognizer cycle is stopped immediately.
15457 * @param {Boolean} [force]
15458 */
15459 stop: function(force) {
15460 this.session.stopped = force ? FORCED_STOP : STOP;
15461 },
15462
15463 /**
15464 * run the recognizers!
15465 * called by the inputHandler function on every movement of the pointers (touches)
15466 * it walks through all the recognizers and tries to detect the gesture that is being made
15467 * @param {Object} inputData
15468 */
15469 recognize: function(inputData) {
15470 var session = this.session;
15471 if (session.stopped) {
15472 return;
15473 }
15474
15475 // run the touch-action polyfill
15476 this.touchAction.preventDefaults(inputData);
15477
15478 var recognizer;
15479 var recognizers = this.recognizers;
15480
15481 // this holds the recognizer that is being recognized.
15482 // so the recognizer's state needs to be BEGAN, CHANGED, ENDED or RECOGNIZED
15483 // if no recognizer is detecting a thing, it is set to `null`
15484 var curRecognizer = session.curRecognizer;
15485
15486 // reset when the last recognizer is recognized
15487 // or when we're in a new session
15488 if (!curRecognizer || (curRecognizer && curRecognizer.state & STATE_RECOGNIZED)) {
15489 curRecognizer = session.curRecognizer = null;
15490 }
15491
15492 var i = 0;
15493 while (i < recognizers.length) {
15494 recognizer = recognizers[i];
15495
15496 // find out if we are allowed try to recognize the input for this one.
15497 // 1. allow if the session is NOT forced stopped (see the .stop() method)
15498 // 2. allow if we still haven't recognized a gesture in this session, or the this recognizer is the one
15499 // that is being recognized.
15500 // 3. allow if the recognizer is allowed to run simultaneous with the current recognized recognizer.
15501 // this can be setup with the `recognizeWith()` method on the recognizer.
15502 if (session.stopped !== FORCED_STOP && ( // 1
15503 !curRecognizer || recognizer == curRecognizer || // 2
15504 recognizer.canRecognizeWith(curRecognizer))) { // 3
15505 recognizer.recognize(inputData);
15506 } else {
15507 recognizer.reset();
15508 }
15509
15510 // if the recognizer has been recognizing the input as a valid gesture, we want to store this one as the
15511 // current active recognizer. but only if we don't already have an active recognizer
15512 if (!curRecognizer && recognizer.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED)) {
15513 curRecognizer = session.curRecognizer = recognizer;
15514 }
15515 i++;
15516 }
15517 },
15518
15519 /**
15520 * get a recognizer by its event name.
15521 * @param {Recognizer|String} recognizer
15522 * @returns {Recognizer|Null}
15523 */
15524 get: function(recognizer) {
15525 if (recognizer instanceof Recognizer) {
15526 return recognizer;
15527 }
15528
15529 var recognizers = this.recognizers;
15530 for (var i = 0; i < recognizers.length; i++) {
15531 if (recognizers[i].options.event == recognizer) {
15532 return recognizers[i];
15533 }
15534 }
15535 return null;
15536 },
15537
15538 /**
15539 * add a recognizer to the manager
15540 * existing recognizers with the same event name will be removed
15541 * @param {Recognizer} recognizer
15542 * @returns {Recognizer|Manager}
15543 */
15544 add: function(recognizer) {
15545 if (invokeArrayArg(recognizer, 'add', this)) {
15546 return this;
15547 }
15548
15549 // remove existing
15550 var existing = this.get(recognizer.options.event);
15551 if (existing) {
15552 this.remove(existing);
15553 }
15554
15555 this.recognizers.push(recognizer);
15556 recognizer.manager = this;
15557
15558 this.touchAction.update();
15559 return recognizer;
15560 },
15561
15562 /**
15563 * remove a recognizer by name or instance
15564 * @param {Recognizer|String} recognizer
15565 * @returns {Manager}
15566 */
15567 remove: function(recognizer) {
15568 if (invokeArrayArg(recognizer, 'remove', this)) {
15569 return this;
15570 }
15571
15572 recognizer = this.get(recognizer);
15573
15574 // let's make sure this recognizer exists
15575 if (recognizer) {
15576 var recognizers = this.recognizers;
15577 var index = inArray(recognizers, recognizer);
15578
15579 if (index !== -1) {
15580 recognizers.splice(index, 1);
15581 this.touchAction.update();
15582 }
15583 }
15584
15585 return this;
15586 },
15587
15588 /**
15589 * bind event
15590 * @param {String} events
15591 * @param {Function} handler
15592 * @returns {EventEmitter} this
15593 */
15594 on: function(events, handler) {
15595 if (events === undefined) {
15596 return;
15597 }
15598 if (handler === undefined) {
15599 return;
15600 }
15601
15602 var handlers = this.handlers;
15603 each(splitStr(events), function(event) {
15604 handlers[event] = handlers[event] || [];
15605 handlers[event].push(handler);
15606 });
15607 return this;
15608 },
15609
15610 /**
15611 * unbind event, leave emit blank to remove all handlers
15612 * @param {String} events
15613 * @param {Function} [handler]
15614 * @returns {EventEmitter} this
15615 */
15616 off: function(events, handler) {
15617 if (events === undefined) {
15618 return;
15619 }
15620
15621 var handlers = this.handlers;
15622 each(splitStr(events), function(event) {
15623 if (!handler) {
15624 delete handlers[event];
15625 } else {
15626 handlers[event] && handlers[event].splice(inArray(handlers[event], handler), 1);
15627 }
15628 });
15629 return this;
15630 },
15631
15632 /**
15633 * emit event to the listeners
15634 * @param {String} event
15635 * @param {Object} data
15636 */
15637 emit: function(event, data) {
15638 // we also want to trigger dom events
15639 if (this.options.domEvents) {
15640 triggerDomEvent(event, data);
15641 }
15642
15643 // no handlers, so skip it all
15644 var handlers = this.handlers[event] && this.handlers[event].slice();
15645 if (!handlers || !handlers.length) {
15646 return;
15647 }
15648
15649 data.type = event;
15650 data.preventDefault = function() {
15651 data.srcEvent.preventDefault();
15652 };
15653
15654 var i = 0;
15655 while (i < handlers.length) {
15656 handlers[i](data);
15657 i++;
15658 }
15659 },
15660
15661 /**
15662 * destroy the manager and unbinds all events
15663 * it doesn't unbind dom events, that is the user own responsibility
15664 */
15665 destroy: function() {
15666 this.element && toggleCssProps(this, false);
15667
15668 this.handlers = {};
15669 this.session = {};
15670 this.input.destroy();
15671 this.element = null;
15672 }
15673};
15674
15675/**
15676 * add/remove the css properties as defined in manager.options.cssProps
15677 * @param {Manager} manager
15678 * @param {Boolean} add
15679 */
15680function toggleCssProps(manager, add) {
15681 var element = manager.element;
15682 if (!element.style) {
15683 return;
15684 }
15685 var prop;
15686 each(manager.options.cssProps, function(value, name) {
15687 prop = prefixed(element.style, name);
15688 if (add) {
15689 manager.oldCssProps[prop] = element.style[prop];
15690 element.style[prop] = value;
15691 } else {
15692 element.style[prop] = manager.oldCssProps[prop] || '';
15693 }
15694 });
15695 if (!add) {
15696 manager.oldCssProps = {};
15697 }
15698}
15699
15700/**
15701 * trigger dom event
15702 * @param {String} event
15703 * @param {Object} data
15704 */
15705function triggerDomEvent(event, data) {
15706 var gestureEvent = document.createEvent('Event');
15707 gestureEvent.initEvent(event, true, true);
15708 gestureEvent.gesture = data;
15709 data.target.dispatchEvent(gestureEvent);
15710}
15711
15712assign(NGHammer, {
15713 INPUT_START: INPUT_START,
15714 INPUT_MOVE: INPUT_MOVE,
15715 INPUT_END: INPUT_END,
15716 INPUT_CANCEL: INPUT_CANCEL,
15717
15718 STATE_POSSIBLE: STATE_POSSIBLE,
15719 STATE_BEGAN: STATE_BEGAN,
15720 STATE_CHANGED: STATE_CHANGED,
15721 STATE_ENDED: STATE_ENDED,
15722 STATE_RECOGNIZED: STATE_RECOGNIZED,
15723 STATE_CANCELLED: STATE_CANCELLED,
15724 STATE_FAILED: STATE_FAILED,
15725
15726 DIRECTION_NONE: DIRECTION_NONE,
15727 DIRECTION_LEFT: DIRECTION_LEFT,
15728 DIRECTION_RIGHT: DIRECTION_RIGHT,
15729 DIRECTION_UP: DIRECTION_UP,
15730 DIRECTION_DOWN: DIRECTION_DOWN,
15731 DIRECTION_HORIZONTAL: DIRECTION_HORIZONTAL,
15732 DIRECTION_VERTICAL: DIRECTION_VERTICAL,
15733 DIRECTION_ALL: DIRECTION_ALL,
15734
15735 Manager: Manager,
15736 Input: Input,
15737 TouchAction: TouchAction,
15738
15739 TouchInput: TouchInput,
15740 MouseInput: MouseInput,
15741 PointerEventInput: PointerEventInput,
15742 TouchMouseInput: TouchMouseInput,
15743 SingleTouchInput: SingleTouchInput,
15744
15745 Recognizer: Recognizer,
15746 AttrRecognizer: AttrRecognizer,
15747 Tap: TapRecognizer,
15748 Pan: PanRecognizer,
15749 Swipe: SwipeRecognizer,
15750 Pinch: PinchRecognizer,
15751 Rotate: RotateRecognizer,
15752 Press: PressRecognizer,
15753
15754 on: addEventListeners,
15755 off: removeEventListeners,
15756 each: each,
15757 merge: merge,
15758 extend: extend,
15759 assign: assign,
15760 inherit: inherit,
15761 bindFn: bindFn,
15762 prefixed: prefixed
15763});
15764
15765// this prevents errors when NGHammer is loaded in the presence of an AMD
15766// style loader but by script tag, not by the loader.
15767var freeGlobal = (typeof window !== 'undefined' ? window : (typeof self !== 'undefined' ? self : {})); // jshint ignore:line
15768freeGlobal.NGHammer = NGHammer;
15769
15770if (typeof define === 'function' && define.amdDISABLED) {
15771 define(function() {
15772 return NGHammer;
15773 });
15774} else if (typeof module != 'undefined' && module.exports) {
15775 module.exports = NGHammer;
15776} else {
15777 window[exportName] = NGHammer;
15778}
15779
15780})(window, document, 'NGHammer');
15781
15782
15783
15784
15785
15786
15787// END NANOGALLERY2
15788// }( jQuery )));
15789}));
15790
15791
15792//##########################################################################################################################
15793//##########################################################################################################################
15794//##########################################################################################################################
15795//##########################################################################################################################
15796//##########################################################################################################################
15797
15798// nanogallery2 auto start whithout javascript call
15799(function(){
15800 'use strict';
15801
15802 function document_ready(callback){
15803 // in case the document is already rendered
15804 if (document.readyState!='loading') callback();
15805 // modern browsers
15806 else if (document.addEventListener) document.addEventListener('DOMContentLoaded', callback);
15807 // IE <= 8
15808 else document.attachEvent('onreadystatechange', function(){
15809 if (document.readyState=='complete') callback();
15810 });
15811 }
15812
15813 document_ready(function(){
15814
15815 // retrieve GALLERIES
15816 var t=document.querySelectorAll('[data-nanogallery2]');
15817 for( var i=0; i < t.length; i++ ) {
15818 jQuery( t[i] ).nanogallery2( jQuery(t[i]).data('nanogallery2') );
15819 }
15820
15821 // retrieve SINGLE ELEMENTS -> ONLY LIGHTBOX / NO GALLERY
15822 var t = document.querySelectorAll('[data-nanogallery2-lightbox]');
15823 for( var i=0; i < t.length; i++ ) {
15824
15825 // set mouse pointer
15826 t[i].classList.add('NGY2ThumbnailLightbox');
15827
15828 // add click event
15829 t[i].addEventListener('click', function(e) {
15830 // disable link tag if <A> element
15831 e.preventDefault();
15832
15833 // default options for standalone lightbox
15834 var options = {
15835 lightboxStandalone: true,
15836 viewerToolbar: { display: false }
15837 };
15838
15839 // group of images
15840 var g = this.dataset.nanogallery2Lgroup;
15841
15842 // Retrieve the lightbox configuration
15843 // it just need to be defined on one of the elements, which will be displayed in the lightbox
15844 var t = document.querySelectorAll('[data-nanogallery2-lightbox]');
15845 for( var i=0; i < t.length; i++ ) {
15846 if( t[i].dataset.nanogallery2Lgroup == g ) {
15847 if( t[i].dataset.nanogallery2Lightbox !== "" ) {
15848 options = jQuery.extend(true, {}, options, jQuery(t[i]).data('nanogallery2Lightbox'));
15849 break;
15850 }
15851 }
15852 }
15853 jQuery( this ).nanogallery2( options );
15854
15855 });
15856
15857 }
15858 });
15859
15860
15861
15862 // jQuery(document).ready(function () {
15863
15864 // var t=document.querySelectorAll('[data-nanogallery2-portable]');
15865 // if( t.length > 0 ) {
15866 // portable mode
15867 // var link = document.createElement('link');
15868 // link.setAttribute("rel", "stylesheet");
15869 // link.setAttribute("type", "text/css");
15870 // link.onload = function(){
15871 // for( var i=0; i < t.length; i++ ) {
15872 // jQuery(t[i]).nanogallery2(jQuery(t[i]).data('nanogallery2-portable'));
15873 // }
15874 // }
15875 // link.setAttribute("href", '//nano.gallery/css/nanogallery2.css');
15876 // document.getElementsByTagName("head")[0].appendChild(link);
15877 // }
15878 // else {
15879 // standard mode
15880
15881 // GALLERIES
15882 // var t=document.querySelectorAll('[data-nanogallery2]');
15883 // for( var i=0; i < t.length; i++ ) {
15884 // jQuery( t[i] ).nanogallery2( jQuery(t[i]).data('nanogallery2') );
15885 // }
15886
15887
15888 // }
15889
15890 // });
15891}).call(null);
15892
15893
15894/**!
15895 * @preserve nanogallery2 - NANOPHOTOSPROVIDER2 data provider
15896 * Homepage: http://nanogallery2.nanostudio.org
15897 * Sources: https://github.com/nanostudio-org/nanogallery2
15898 *
15899 * License: GPLv3 and commercial licence
15900 *
15901*/
15902
15903// ########################################################
15904// ##### nanogallery2 - module NANOPHOTOSPROVIDER2 #####
15905// ########################################################
15906
15907
15908(function (factory) {
15909 "use strict";
15910 if (typeof define === 'function' && define.amd) {
15911 // AMD. Register as an anonymous module.
15912 define(['jquery', 'nanogallery2'], factory);
15913 } else if (typeof exports === 'object' && typeof require === 'function') {
15914 // Browserify
15915 factory(require(['jquery', 'nanogallery2']));
15916 } else {
15917 // Browser globals
15918 factory(jQuery);
15919 }
15920}(function ($) {
15921// ;(function ($) {
15922
15923 jQuery.nanogallery2.data_nano_photos_provider2 = function (instance, fnName){
15924 var G = instance; // current nanogallery2 instance
15925
15926 /** @function AlbumGetContent */
15927 var AlbumGetContent = function(albumID, fnToCall, fnParam1, fnParam2) {
15928
15929 var albumIdx = NGY2Item.GetIdx(G, albumID);
15930
15931 // title is identical to ID (only for albums)
15932 if( instance.I[albumIdx].title == '' ) {
15933 instance.I[albumIdx].title = JsonConvertCharset(albumID);
15934 }
15935
15936 // Build the URL
15937 var url = G.O.dataProvider + '?albumID='+albumID; // which album
15938
15939 // all thumbnails sizes (for responsive display)
15940 url += '&hxs=' + G.tn.settings.getH(G.GOM.curNavLevel, 'xs');
15941 url += '&wxs=' + G.tn.settings.getW(G.GOM.curNavLevel, 'xs');
15942 url += '&hsm=' + G.tn.settings.getH(G.GOM.curNavLevel, 'sm');
15943 url += '&wsm=' + G.tn.settings.getW(G.GOM.curNavLevel, 'sm');
15944 url += '&hme=' + G.tn.settings.getH(G.GOM.curNavLevel, 'me');
15945 url += '&wme=' + G.tn.settings.getW(G.GOM.curNavLevel, 'me');
15946 url += '&hla=' + G.tn.settings.getH(G.GOM.curNavLevel, 'la');
15947 url += '&wla=' + G.tn.settings.getW(G.GOM.curNavLevel, 'la');
15948 url += '&hxl=' + G.tn.settings.getH(G.GOM.curNavLevel, 'xl');
15949 url += '&wxl=' + G.tn.settings.getW(G.GOM.curNavLevel, 'xl');
15950 // url += '&wxs=' + G.tn.settings.width[G.GOM.curNavLevel].xs;
15951 // url += '&hxs=' + G.tn.settings.height[G.GOM.curNavLevel].xs;
15952 // url += '&wsm=' + G.tn.settings.width[G.GOM.curNavLevel].sm;
15953 // url += '&hsm=' + G.tn.settings.height[G.GOM.curNavLevel].sm;
15954 // url += '&wme=' + G.tn.settings.width[G.GOM.curNavLevel].me;
15955 // url += '&hme=' + G.tn.settings.height[G.GOM.curNavLevel].me;
15956 // url += '&wla=' + G.tn.settings.width[G.GOM.curNavLevel].la;
15957 // url += '&hla=' + G.tn.settings.height[G.GOM.curNavLevel].la;
15958 // url += '&wxl=' + G.tn.settings.width[G.GOM.curNavLevel].xl;
15959 // url += '&hxl=' + G.tn.settings.height[G.GOM.curNavLevel].xl;
15960
15961 PreloaderDisplay( true );
15962
15963 jQuery.ajaxSetup({ cache: false });
15964 jQuery.support.cors = true;
15965 try {
15966
15967 var tId = setTimeout( function() {
15968 // workaround to handle JSONP (cross-domain) errors
15969 PreloaderDisplay(false);
15970 NanoAlert(G, 'Could not retrieve nanoPhotosProvider2 data (timeout).');
15971 }, 60000 );
15972
15973 if( G.O.debugMode ) { console.log('nanoPhotosProvider2 URL: ' + url); }
15974
15975 jQuery.getJSON(url, function(data, status, xhr) {
15976 clearTimeout( tId );
15977 PreloaderDisplay( false );
15978 JsonParseData(albumIdx, data);
15979
15980 if( data.nano_status == 'ok' ) {
15981 AlbumPostProcess( albumID );
15982 if( fnToCall !== null && fnToCall !== undefined) {
15983 fnToCall( fnParam1, fnParam2, null );
15984 }
15985 }
15986 else {
15987 NanoAlert(G, 'Could not retrieve nanoPhotosProvider2 data. Error: ' + data.nano_status + ' - ' + data.nano_message);
15988 }
15989 })
15990 .fail( function(jqxhr, textStatus, error) {
15991 clearTimeout( tId );
15992 PreloaderDisplay( false );
15993
15994 var k=''
15995 for(var key in jqxhr) {
15996 k+= key + '=' + jqxhr[key] +'<br>';
15997 }
15998 var err = textStatus + ', ' + error + ' ' + k + '<br><br>URL:'+url;
15999 NanoAlert(G, 'Could not retrieve nanoPhotosProvider2 data. Error: ' + err);
16000
16001 });
16002
16003 }
16004 catch(e) {
16005 NanoAlert(G, 'Could not retrieve nanoPhotosProvider2 data. Error: ' + e);
16006 }
16007 }
16008
16009
16010 function JsonConvertCharset( str ) {
16011
16012 return decodeURIComponent(str);
16013
16014
16015 // Pb %C3%A9 --> %E9
16016 // in UTF-8: \u00e9=\xe9 (e9 = hex value)
16017 // switch( G.O.dataCharset.toUpperCase() ) {
16018 // case 'UTF-8': // Apache Windows
16019 // return decodeURI(str); // do not use decodeURIComponent (would convert slash also)
16020 // break;
16021 // case 'Latin': // Apache Linux
16022 // default :
16023 // return escape(str);
16024 // break;
16025 // }
16026 }
16027
16028 function JsonParseData(albumIdx, data) {
16029 if( G.O.debugMode ) {
16030 console.log('nanoPhotosProvider2 parse data:');
16031 console.dir(data);
16032 }
16033
16034
16035 var foundAlbumID = false;
16036 var nb = 0;
16037
16038 // loop each item
16039 jQuery.each( data.album_content, function( i, item ){
16040
16041 // base URL where the images are stored
16042 var baseURL = G.O.dataProvider.substring(0, G.O.dataProvider.indexOf('nano_photos_provider2.php'));
16043
16044 // image URL
16045 var src = baseURL + JsonConvertCharset( item.src );
16046
16047 // item title
16048 var title = item.title;
16049
16050 // item description ( '_' are replaced with ' ' )
16051 var description = item.description.split('_').join(' ');
16052
16053 // item kind ('album' or 'image')
16054 var kind = 'image';
16055 if( item.kind !== undefined && item.kind.length > 0 ) {
16056 kind = item.kind;
16057 }
16058
16059 // item ID
16060 var ID=item.ID;
16061
16062 var filterAlbum = false;
16063 if( kind == 'album' ) {
16064 // check if album name is filtered
16065 if( !FilterAlbumName(title, ID) ) { filterAlbum = true; }
16066 // on gallery initialization : if an album is defined, do not display sub-albums (not supported)
16067 if( G.O.album != '' || G.O.photoset != '' ) { filterAlbum = true; }
16068 }
16069
16070 // if( kind == 'image' || (kind == 'album' && FilterAlbumName(title, ID)) ) {
16071 if( kind == 'image' || !filterAlbum ) {
16072
16073 var albumID = 0;
16074 if( item.albumID !== undefined ) {
16075 albumID = item.albumID;
16076 foundAlbumID = true;
16077 }
16078
16079 var tags = (item.tags === undefined) ? '' : item.tags;
16080 var newItem = NGY2Item.New( G, title.split('_').join(' ') , description, ID, albumID, kind, tags );
16081 newItem.setMediaURL( src, 'img');
16082
16083 // dominant colorS as a gif
16084 if( item.dcGIF !== undefined ) {
16085 newItem.imageDominantColors = 'data:image/gif;base64,' + item.dcGIF;
16086 }
16087 // dominant color as hex rgb value
16088 if( item.dc !== undefined && item.dc !== '' ) {
16089 newItem.imageDominantColor = item.dc;
16090 }
16091
16092 if( kind == 'album' ) {
16093 // number of items in album
16094 newItem.numberItems = item.cnt;
16095 }
16096 else {
16097 // image size
16098 newItem.imageWidth = item.imgWidth;
16099 newItem.imageHeight = item.imgHeight;
16100 }
16101
16102 // item download URL
16103 if( item.originalURL != '' ) {
16104 newItem.downloadURL = baseURL+JsonConvertCharset(item.originalURL);
16105 }
16106
16107 // retrieve responsive thumbnails urls and sizes
16108 var cnl = G.GOM.curNavLevel; // current navigation level ('L1' or 'LN');
16109 var l=['xs', 'sm', 'me', 'la', 'xl'];
16110 for( var n = 0; n < l.length; n++ ) {
16111 newItem.thumbs.url[cnl][l[n]] = baseURL + JsonConvertCharset(item.t_url[n]);
16112 newItem.thumbs.width[cnl][l[n]] = parseInt(item.t_width[n]);
16113 newItem.thumbs.height[cnl][l[n]] = parseInt(item.t_height[n]);
16114 }
16115
16116 // post-process callback
16117 var fu = G.O.fnProcessData;
16118 if( fu !== null ) {
16119 typeof fu == 'function' ? fu(newItem, G.O.dataProvider, data) : window[fu](newItem, G.O.dataProvider, data);
16120 }
16121
16122 }
16123 });
16124
16125 G.I[albumIdx].contentIsLoaded = true; // album's content is ready
16126 }
16127
16128
16129 // -----------
16130 // Initialize
16131 function Init() {
16132
16133 }
16134
16135
16136 // shortcuts to NGY2Tools functions (with context)
16137 var PreloaderDisplay = NGY2Tools.PreloaderDisplay.bind(G);
16138 // var NanoAlert = NGY2Tools.NanoAlert.bind(G);
16139 var NanoAlert = NGY2Tools.NanoAlert;
16140 // var GetImageTitleFromURL = NGY2Tools.GetImageTitleFromURL.bind(G);
16141 var FilterAlbumName = NGY2Tools.FilterAlbumName.bind(G);
16142 var AlbumPostProcess = NGY2Tools.AlbumPostProcess.bind(G);
16143
16144 switch( fnName ){
16145 case 'GetHiddenAlbums':
16146 break;
16147 case 'AlbumGetContent':
16148 var albumID = arguments[2],
16149 callback = arguments[3],
16150 cbParam1 = arguments[4],
16151 cbParam2 = arguments[5];
16152 AlbumGetContent(albumID, callback, cbParam1, cbParam2);
16153 break;
16154 case 'Init':
16155 Init();
16156 break;
16157 case '':
16158 break;
16159 }
16160
16161 };
16162
16163// END NANOPHOTOSPROVIDER DATA SOURCE FOR NANOGALLERY2
16164// }( jQuery ));
16165}));
16166
16167
16168
16169
16170/**!
16171 * @preserve nanogallery2 - GOOGLE PHOTOS data provider
16172 * Homepage: http://nanogallery2.nanostudio.org
16173 * Sources: https://github.com/nanostudio-org/nanogallery2
16174 *
16175 * License: GPLv3 and commercial licence
16176 *
16177*/
16178
16179// ###################################################
16180// ##### nanogallery2 - module for GOOGLE PHOTOS #####
16181// ##### requires nanogp2 #####
16182// ###################################################
16183
16184
16185(function (factory) {
16186 "use strict";
16187 if (typeof define === 'function' && define.amd) {
16188 // AMD. Register as an anonymous module.
16189 define(['jquery', 'nanogallery2'], factory);
16190 } else if (typeof exports === 'object' && typeof require === 'function') {
16191 // Browserify
16192 factory(require(['jquery', 'nanogallery2']));
16193 } else {
16194 // Browser globals
16195 factory(jQuery);
16196 }
16197}(function ($) {
16198// ;(function ($) {
16199
16200 jQuery.nanogallery2.data_google2 = function (instance, fnName){
16201 var G=instance; // current nanogallery2 instance
16202
16203
16204 /** @function AlbumGetContent */
16205 var AlbumGetContent = function(albumID, fnToCall, fnParam1, fnParam2) {
16206
16207 var url = '';
16208 var kind = 'image';
16209 var albumIdx = NGY2Item.GetIdx(G, albumID);
16210
16211 var maxResults='';
16212 if( G.galleryMaxItems.Get() > 0 ) {
16213 maxResults = '&max-results=' + G.galleryMaxItems.Get();
16214 }
16215
16216 var gat=''; // global authorization (using the BUILDER)
16217 if( typeof ngy2_pwa_at !== 'undefined' ) {
16218 gat=ngy2_pwa_at;
16219 }
16220
16221 if( albumID == 0 ) {
16222 // RETRIEVE THE LIST OF ALBUMS
16223 if( gat != '' ) {
16224 // in builder
16225 // url += '?alt=json&v=3&kind=album&deprecation-extension=true&thumbsize='+G.picasa.thumbSizes+maxResults+'&rnd=' + (new Date().getTime()) + '&access_token=' + gat;
16226 url = 'https://photoslibrary.googleapis.com/v1/albums';
16227 }
16228 else {
16229 // NANOGP2
16230 // url=G.O.google2URL + '?nguserid='+G.O.userID+'&alt=json&v=3&kind=album&thumbsize='+G.picasa.thumbSizes+maxResults+'&rnd=' + (new Date().getTime());
16231 url = G.O.google2URL + '?nguserid=' + G.O.userID + '&alt=json&v=3&kind=album' + maxResults + '&rnd=' + (new Date().getTime());
16232 }
16233 kind='album';
16234
16235 }
16236 else {
16237 // RETRIEVE THE CONTENT OF ONE ALBUM (=MEDIAS)
16238 if( gat != '' ) {
16239 // in builder
16240 // url += '/albumid/'+albumID+'?alt=json&kind=photo&deprecation-extension=true&thumbsize='+G.picasa.thumbSizes+maxResults+'&imgmax=d&access_token=' + gat;
16241 // url += '/albumid/'+albumID+'?alt=json&kind=photo&deprecation-extension=true&thumbsize='+G.picasa.thumbSizes+maxResults+'&imgmax=d&access_token=' + gat;
16242 url = 'https://photoslibrary.googleapis.com/v1/mediaItems:search';
16243 }
16244 else {
16245 // nanogp
16246 // url = G.O.google2URL + '?nguserid='+G.O.userID+'&ngalbumid='+albumID+'&alt=json&v=3&kind=photo&thumbsize='+G.picasa.thumbSizes+maxResults+'&imgmax=d';
16247 url = G.O.google2URL + '?nguserid=' + G.O.userID + '&ngalbumid=' + albumID + '&alt=json&v=3&kind=photo&' + maxResults;
16248 }
16249 }
16250
16251 if( G.O.debugMode ) { console.log('Google Photos URL: ' + url); }
16252
16253 PreloaderDisplay(true);
16254 jQuery.ajaxSetup({ cache: false });
16255 jQuery.support.cors = true;
16256 try {
16257 var tId = setTimeout( function() {
16258 // workaround to handle JSONP (cross-domain) errors
16259 PreloaderDisplay(false);
16260 NanoAlert('Could not retrieve AJAX data...');
16261 }, 60000 );
16262
16263 jQuery.getJSON( url + '&callback=?', function(data) {
16264
16265 if( data.nano_status == 'error' ) {
16266 clearTimeout(tId);
16267 PreloaderDisplay(false);
16268 NanoAlert(G, "Could not retrieve Google data. Error: " + data.nano_message);
16269 return;
16270 }
16271 clearTimeout(tId);
16272 PreloaderDisplay(false);
16273 GoogleParseData( albumIdx, kind, data );
16274 AlbumPostProcess(albumID);
16275 if( fnToCall !== null && fnToCall !== undefined) {
16276 fnToCall( fnParam1, fnParam2, null );
16277 }
16278
16279 })
16280 .fail( function(jqxhr, textStatus, error) {
16281 clearTimeout(tId);
16282 PreloaderDisplay(false);
16283
16284 var k=''
16285 for(var key in jqxhr) {
16286 k+= key + '=' + jqxhr[key] +'<br>';
16287 }
16288 var err = textStatus + ', ' + error + ' ' + k + '<br><br>URL:'+url;
16289 NanoAlert(G, "Could not retrieve Google data. Error: " + err);
16290 });
16291 }
16292 catch(e) {
16293 NanoAlert(G, "Could not retrieve Google data. Error: " + e);
16294 }
16295 }
16296
16297
16298 // -----------
16299 // Retrieve items from a Google Photos data stream
16300 // items can be images/viedos or albums
16301 function GoogleParseData(albumIdx, kind, data) {
16302
16303 if( G.O.debugMode ) {
16304 console.log('Google Photos data:');
16305 console.dir(data);
16306 }
16307 var albumID = G.I[albumIdx].GetID();
16308
16309 // iterate and parse each item
16310 jQuery.each(data, function(i,data){
16311
16312 if( typeof data === 'object' && data !== null ) { // only objects
16313
16314 var itemDescription = '';
16315 var itemTitle = '';
16316 if( kind == 'image') {
16317 if (data.description !== undefined ){
16318 itemDescription = data.description
16319 }
16320 if( G.O.thumbnailLabel.get('title') != '' ) {
16321 itemTitle = GetImageTitleFromURL( data.filename );
16322 }
16323 }
16324 else {
16325 itemTitle = data.title;
16326 }
16327 if( itemTitle == undefined ) {
16328 // may happen...
16329 itemTitle = '';
16330 }
16331
16332 var itemID = data.id;
16333 if( kind == 'album' ) {
16334 if( !FilterAlbumName(itemTitle, itemID) || data.coverPhotoBaseUrl == undefined ) {
16335 return true;
16336 }
16337 }
16338
16339 // create ngy2 item
16340 var newItem = NGY2Item.New( G, itemTitle, itemDescription, itemID, albumID, kind, '' );
16341
16342 var width = 0;
16343 var height = 0;
16344
16345 // set the image src
16346 var src = '';
16347 if( kind == 'image' ) {
16348 src = data.baseUrl;
16349 if( !G.O.viewerZoom && G.O.viewerZoom != undefined ) {
16350 if( window.screen.width > window.screen.height ) {
16351 src += '=w' + window.screen.width;
16352 }
16353 else {
16354 src = s + '=h' + window.screen.height;
16355 }
16356 }
16357 else {
16358 // use full resolution image
16359 src += '=h' + data.mediaMetadata.height + '-w' + data.mediaMetadata.width;
16360
16361 // use original image
16362 // src += '=d';
16363 }
16364
16365 // image's URL
16366 newItem.setMediaURL( src, 'img');
16367
16368 // image size
16369 if( data.mediaMetadata.width !== undefined ) {
16370 newItem.imageWidth = parseInt(data.mediaMetadata.width);
16371 width = newItem.imageWidth;
16372 }
16373 if( data.mediaMetadata.height !== undefined ) {
16374 newItem.imageHeight=parseInt(data.mediaMetadata.height);
16375 height = newItem.imageHeight;
16376 }
16377
16378 // if( data.media$group != null && data.media$group.media$credit != null && data.media$group.media$credit.length > 0 ) {
16379 // newItem.author=data.media$group.media$credit[0].$t;
16380 // }
16381
16382 // Photo
16383 if( data.mediaMetadata.photo !== undefined ) {
16384 // exif data
16385 if( data.mediaMetadata.photo.exposureTime != undefined ) {
16386 newItem.exif.exposure = data.mediaMetadata.photo.exposureTime;
16387 }
16388 if( data.mediaMetadata.photo.focalLength != undefined ) {
16389 newItem.exif.focallength = data.mediaMetadata.photo.focalLength;
16390 }
16391 if( data.mediaMetadata.photo.apertureFNumber != undefined ) {
16392 newItem.exif.fstop = data.mediaMetadata.photo.apertureFNumber;
16393 }
16394 if( data.mediaMetadata.photo.isoEquivalent != undefined ) {
16395 newItem.exif.iso = data.mediaMetadata.photo.isoEquivalent;
16396 }
16397 if( data.mediaMetadata.photo.cameraModel != undefined ) {
16398 newItem.exif.model = data.mediaMetadata.photo.cameraModel;
16399 }
16400 }
16401
16402 // Video
16403 if( data.mediaMetadata.video !== undefined ) {
16404 if( data.mediaMetadata.video.cameraModel != undefined ) {
16405 newItem.exif.model = data.mediaMetadata.video.cameraModel;
16406 }
16407
16408 newItem.downloadURL = data.baseUrl + '=dv'; // set the download URL for the video
16409
16410 // newItem.mediaKind = 'selfhosted';
16411 // newItem.mediaMarkup = '<video controls class="nGY2ViewerMedia"><source src="'+ newItem.src +'" type="video/'+ 'video/mp4' +'" preload="auto">Your browser does not support the video tag (HTML 5).</video>';
16412 }
16413
16414 }
16415 else {
16416 // newItem.author = data.author[0].name.$t;
16417 newItem.numberItems = data.mediaItemsCount;
16418 }
16419
16420 // set the URL of the thumbnails images
16421 newItem.thumbs=GoogleThumbSetSizes2('l1', newItem.thumbs, data, kind, height, width );
16422 newItem.thumbs=GoogleThumbSetSizes2('lN', newItem.thumbs, data, kind,height ,width );
16423
16424 // post-process callback
16425 var fu = G.O.fnProcessData;
16426 if( fu !== null ) {
16427 typeof fu == 'function' ? fu(newItem, 'google2', data) : window[fu](newItem, 'google2', data);
16428 }
16429
16430 }
16431 });
16432
16433 G.I[albumIdx].contentIsLoaded = true; // album's content is ready
16434 }
16435
16436 // -----------
16437 // Set thumbnail sizes (width and height) and URLs (for all resolutions (xs, sm, me, la, xl) and levels (l1, lN)
16438 function GoogleThumbSetSizes2(level, tn, data, kind, height, width ) {
16439 var sizes=['xs','sm','me','la','xl'];
16440
16441 for(var i=0; i<sizes.length; i++ ) {
16442
16443 // media
16444 if( kind == 'image' ) {
16445 if( G.tn.settings.width[level][sizes[i]] == 'auto' ) {
16446 let ratio1 = width / height;
16447 tn.height[level][sizes[i]] = G.tn.settings.getH(level, sizes[i]);
16448 tn.width[level][sizes[i]] = G.tn.settings.getH(level, sizes[i]) * ratio1;
16449 tn.url[level][sizes[i]] = data.baseUrl + '=h' + G.tn.settings.getH(level, sizes[i]);
16450 continue;
16451 }
16452 if( G.tn.settings.height[level][sizes[i]] == 'auto' ) {
16453 let ratio1 = height / width;
16454 tn.width[level][sizes[i]] = G.tn.settings.getW(level, sizes[i]);
16455 tn.height[level][sizes[i]] = G.tn.settings.getW(level, sizes[i]) * ratio1;
16456 tn.url[level][sizes[i]] = data.baseUrl + '=w' + G.tn.settings.getW(level, sizes[i]);
16457 continue;
16458 }
16459
16460 tn.height[level][sizes[i]] = G.tn.settings.getH(level, sizes[i]);
16461 tn.width[level][sizes[i]] = G.tn.settings.getW(level, sizes[i]);
16462 tn.url[level][sizes[i]] = data.baseUrl + '=w' + G.tn.settings.getW(level, sizes[i]);
16463
16464 }
16465
16466 // album
16467 if( kind == 'album' ) {
16468 if( G.tn.settings.width[level][sizes[i]] == 'auto' ) {
16469 tn.url[level][sizes[i]]= data.coverPhotoBaseUrl + '=h' + G.tn.settings.getH(level, sizes[i]);
16470 continue;
16471 }
16472 if( G.tn.settings.height[level][sizes[i]] == 'auto' ) {
16473 tn.url[level][sizes[i]]= data.coverPhotoBaseUrl + '=w' + G.tn.settings.getW(level, sizes[i]);
16474 continue;
16475 }
16476 // var w = G.tn.settings.mosaic[level + 'Factor']['w'][sizes[i]];
16477 tn.url[level][sizes[i]]= data.coverPhotoBaseUrl + '=h' + G.tn.settings.getH(level, sizes[i]) + '-w' + G.tn.settings.getW(level, sizes[i]);
16478
16479 }
16480 }
16481
16482 return tn;
16483 }
16484
16485
16486
16487 // -----------
16488 // Initialization
16489 function Init() {
16490 }
16491
16492
16493 // shortcuts to NGY2Tools functions (with context)
16494 var PreloaderDisplay = NGY2Tools.PreloaderDisplay.bind(G);
16495 // var NanoAlert = NGY2Tools.NanoAlert.bind(G);
16496 var NanoAlert = NGY2Tools.NanoAlert;
16497 var GetImageTitleFromURL = NGY2Tools.GetImageTitleFromURL.bind(G);
16498 var FilterAlbumName = NGY2Tools.FilterAlbumName.bind(G);
16499 var AlbumPostProcess = NGY2Tools.AlbumPostProcess.bind(G);
16500
16501 switch( fnName ){
16502 case 'AlbumGetContent':
16503 var albumID = arguments[2],
16504 callback2 = arguments[3],
16505 cbParam1 = arguments[4],
16506 cbParam2 = arguments[5];
16507 AlbumGetContent(albumID, callback2, cbParam1, cbParam2);
16508 break;
16509 case 'Init':
16510 Init();
16511 break;
16512 case '':
16513 break;
16514 }
16515
16516 };
16517
16518// END GOOGLE DATA SOURCE FOR NANOGALLERY2
16519// }( jQuery ));
16520}));
16521
16522
16523
16524
16525/**!
16526 * @preserve nanogallery2 - FLICKR data provider
16527 * Homepage: http://nanogallery2.nanostudio.org
16528 * Sources: https://github.com/nanostudio-org/nanogallery2
16529 *
16530 * License: GPLv3 and commercial licence
16531 *
16532*/
16533
16534// ############################################
16535// ##### nanogallery2 - module for FLICKR #####
16536// ############################################
16537
16538
16539(function (factory) {
16540 "use strict";
16541 if (typeof define === 'function' && define.amd) {
16542 // AMD. Register as an anonymous module.
16543 define(['jquery', 'nanogallery2'], factory);
16544 } else if (typeof exports === 'object' && typeof require === 'function') {
16545 // Browserify
16546 factory(require(['jquery', 'nanogallery2']));
16547 } else {
16548 // Browser globals
16549 factory(jQuery);
16550 }
16551}(function ($) {
16552// ;(function ($) {
16553
16554 jQuery.nanogallery2.data_flickr = function (instance, fnName){
16555 var G = instance; // current nanogallery2 instance
16556
16557 // ### Flickr
16558 // Details: http://www.flickr.com/services/api/misc.urls.html
16559 var Flickr = {
16560 url: function() {
16561 // Flickr API Going SSL-Only on June 27th, 2014
16562 return 'https://api.flickr.com/services/rest/';
16563 },
16564 thumbSize:' sq',
16565 thumbAvailableSizes : new Array(75, 100, 150, 240, 500, 640),
16566 thumbAvailableSizesStr : new Array('sq', 't', 'q', 's', 'm', 'z'),
16567 photoSize : '0',
16568 photoAvailableSizes : new Array(75, 100, 150, 240, 500, 640, 1024, 1024, 1600, 2048, 10000),
16569 photoAvailableSizesStr : new Array('sq', 't', 'q', 's', 'm', 'z', 'b', 'l', 'h', 'k', 'o'),
16570 ApiKey : "2f0e634b471fdb47446abcb9c5afebdc"
16571 };
16572
16573
16574 /** @function AlbumGetContent */
16575 var AlbumGetContent = function(albumID, fnToCall, fnParam1, fnParam2) {
16576
16577 var albumIdx = NGY2Item.GetIdx(G, albumID);
16578 var url = '';
16579 var kind = 'image';
16580 // photos
16581 if( G.O.photoset.toUpperCase() == 'NONE' || G.O.album.toUpperCase() == 'NONE' ) {
16582 // get photos from full photostream
16583 url = Flickr.url() + "?&method=flickr.people.getPublicPhotos&api_key=" + Flickr.ApiKey + "&user_id="+G.O.userID+"&extras=description,views,tags,url_o,url_sq,url_t,url_q,url_s,url_m,url_z,url_b,url_h,url_k&per_page=500&format=json";
16584 }
16585 else
16586 if( G.I[albumIdx].GetID() == 0 ) {
16587 // retrieve the list of albums
16588 url = Flickr.url() + "?&method=flickr.photosets.getList&api_key=" + Flickr.ApiKey + "&user_id="+G.O.userID+"&per_page=500&primary_photo_extras=tags,url_o,url_sq,url_t,url_q,url_s,url_m,url_l,url_z,url_b,url_h,url_k&format=json";
16589 kind='album';
16590 }
16591 else {
16592 // photos from one specific photoset
16593 url = Flickr.url() + "?&method=flickr.photosets.getPhotos&api_key=" + Flickr.ApiKey + "&photoset_id="+G.I[albumIdx].GetID()+"&extras=description,views,tags,url_o,url_sq,url_t,url_q,url_s,url_m,url_l,url_z,url_b,url_h,url_k&format=json";
16594 }
16595
16596 if( G.O.debugMode ) { console.log('Flickr URL: ' + url); }
16597
16598 PreloaderDisplay(true);
16599 jQuery.ajaxSetup({ cache: false });
16600 jQuery.support.cors = true;
16601
16602 var tId = setTimeout( function() {
16603 // workaround to handle JSONP (cross-domain) errors
16604 PreloaderDisplay(false);
16605 NanoAlert(G, 'Could not retrieve AJAX data...');
16606 }, 60000 );
16607
16608 var sourceData=[];
16609
16610 // Process the downloaded data
16611 var FlickrGetDone = function() {
16612 clearTimeout(tId);
16613 PreloaderDisplay(false);
16614
16615 // go through sourceData, and exclude blacklisted tags
16616 sourceData = FilterByTags(sourceData, G.O.tagBlockList);
16617
16618 if( kind == 'album' ) {
16619 FlickrParsePhotoSets(albumIdx, albumID, sourceData);
16620 }
16621 else {
16622 FlickrParsePhotos(albumIdx, albumID, sourceData);
16623 }
16624
16625 AlbumPostProcess( albumID );
16626
16627 if( fnToCall !== null && fnToCall !== undefined) {
16628 fnToCall( fnParam1, fnParam2, null );
16629 }
16630 }
16631
16632 // download one page of data (=500 entries)
16633 var FlickrGetOnePage = function( url, page ) {
16634 jQuery.getJSON( url + '&page=' + page + '&jsoncallback=?', function(data, status, xhr) {
16635
16636 var pages=0;
16637 if( kind == 'album' ) {
16638 if( data.stat !== undefined && data.stat === 'fail' ) {
16639 NanoAlert(G, "Could not retrieve Flickr album list: " + data.message + " (code: "+data.code+").");
16640 return false;
16641 }
16642 sourceData=sourceData.concat(data.photosets.photoset);
16643 pages=data.photosets.pages;
16644 }
16645 else {
16646 if( G.O.photoset.toUpperCase() == 'NONE' || G.O.album.toUpperCase() == 'NONE' ) {
16647 // content of full photoset
16648 sourceData=sourceData.concat(data.photos.photo);
16649 pages=data.photos.pages;
16650 }
16651 else {
16652 // content of one album
16653 if( data.stat !== undefined && data.stat === 'fail' ) {
16654 NanoAlert(G, "Could not retrieve Flickr album: " + data.message + " (code: "+data.code+").");
16655 return false;
16656 }
16657 if( G.I[albumIdx].title == '' ) {
16658 G.I[albumIdx].title=data.photoset.title;
16659 }
16660 sourceData=sourceData.concat(data.photoset.photo);
16661 pages=data.photoset.pages;
16662 }
16663
16664 }
16665
16666 if( pages > page ) {
16667 FlickrGetOnePage(url, page+1);
16668 }
16669 else {
16670 FlickrGetDone();
16671 }
16672 })
16673 .fail( function(jqxhr, textStatus, error) {
16674 clearTimeout(tId);
16675 PreloaderDisplay(false);
16676 NanoAlert(G, "Could not retrieve Flickr ajax data: " + textStatus + ', ' + error);
16677 });
16678
16679 }
16680
16681 FlickrGetOnePage(url, 1);
16682
16683 }
16684
16685
16686
16687 // -----------
16688 // Retrieve items for one Flickr photoset
16689 function FlickrParsePhotos( albumIdx, albumID, source ) {
16690
16691 if( G.O.debugMode ) {
16692 console.log('Flickr parse photos:');
16693 console.dir(source);
16694 }
16695
16696 jQuery.each(source, function(i,item){
16697
16698 var itemID = item.id;
16699
16700 var imgUrl=item.url_sq; //fallback size
16701
16702 // get the title
16703 var itemTitle = item.title;
16704 if( G.O.thumbnailLabel.get('title') != '' ) {
16705 itemTitle=GetImageTitleFromURL(imgUrl);
16706 }
16707
16708 // get the description
16709 var itemDescription=item.description._content;
16710
16711 // retrieve the image size with highest available resolution
16712 var imgW=75, imgH=75;
16713 var start=Flickr.photoAvailableSizesStr.length-1;
16714 if( G.O.flickrSkipOriginal ) { start--; }
16715 for( var i = start; i>=0 ; i-- ) {
16716 if( item['url_'+Flickr.photoAvailableSizesStr[i]] != undefined ) {
16717 imgUrl=item['url_'+Flickr.photoAvailableSizesStr[i]];
16718 imgW=parseInt(item['width_'+Flickr.photoAvailableSizesStr[i]]);
16719 imgH=parseInt(item['height_'+Flickr.photoAvailableSizesStr[i]]);
16720 break;
16721 }
16722 }
16723
16724 var sizes = {};
16725 for( var p in item ) {
16726 if( p.indexOf('height_') == 0 || p.indexOf('width_') == 0 || p.indexOf('url_') == 0 ) {
16727 sizes[p]=item[p];
16728 }
16729 }
16730
16731 // tags
16732 var tags = item.tags !== undefined ? item.tags : '';
16733
16734 // create item
16735 var newItem = NGY2Item.New( G, itemTitle, itemDescription, itemID, albumID, 'image', tags );
16736
16737 // add image
16738 newItem.setMediaURL( imgUrl, 'img');
16739 newItem.imageWidth = imgW;
16740 newItem.imageHeight = imgH;
16741
16742
16743 // add thumbnails
16744 var tn = {
16745 url: { l1 : { xs:'', sm:'', me:'', la:'', xl:'' }, lN : { xs:'', sm:'', me:'', la:'', xl:'' } },
16746 width: { l1 : { xs:0, sm:0, me:0, la:0, xl:0 }, lN : { xs:0, sm:0, me:0, la:0, xl:0 } },
16747 height: { l1 : { xs:0, sm:0, me:0, la:0, xl:0 }, lN : { xs:0, sm:0, me:0, la:0, xl:0 } }
16748 };
16749 tn = FlickrRetrieveImages(tn, item, 'l1' );
16750 tn = FlickrRetrieveImages(tn, item, 'lN' );
16751 newItem.thumbs=tn;
16752
16753 // post-process callback
16754 var fu = G.O.fnProcessData;
16755 if( fu !== null ) {
16756 typeof fu == 'function' ? fu(newItem, 'flickr', item) : window[fu](newItem, 'flickr', item);
16757 }
16758
16759
16760 });
16761 G.I[albumIdx].contentIsLoaded=true;
16762
16763 }
16764
16765
16766
16767 // -----------
16768 // Retrieve the list of Flickr photosets
16769 function FlickrParsePhotoSets( albumIdx, albumID, source ) {
16770
16771 if( G.O.debugMode ) {
16772 console.log('Flickr parse list of albums:');
16773 console.dir(source);
16774 }
16775
16776 jQuery.each(source, function(i,item){
16777 //Get the title
16778 var itemTitle = item.title._content;
16779
16780 if( item.visibility_can_see_set == 0 ) { return true; } // skip it
16781
16782 if( FilterAlbumName(itemTitle, item.id) ) {
16783 var itemID=item.id;
16784 //Get the description
16785 var itemDescription = item.description._content != undefined ? item.description._content : '';
16786
16787 var sizes = {};
16788 for( var p in item.primary_photo_extras) {
16789 sizes[p] = item.primary_photo_extras[p];
16790 }
16791 var tags='';
16792 if( item.primary_photo_extras !== undefined ) {
16793 if( item.primary_photo_extras.tags !== undefined ) {
16794 tags = item.primary_photo_extras.tags;
16795 }
16796 }
16797
16798 var newItem = NGY2Item.New( G, itemTitle, itemDescription, itemID, albumID, 'album', tags );
16799 newItem.numberItems = item.photos;
16800 newItem.thumbSizes = sizes;
16801
16802 var tn = {
16803 url: { l1 : { xs:'', sm:'', me:'', la:'', xl:'' }, lN : { xs:'', sm:'', me:'', la:'', xl:'' } },
16804 width: { l1 : { xs:0, sm:0, me:0, la:0, xl:0 }, lN : { xs:0, sm:0, me:0, la:0, xl:0 } },
16805 height: { l1 : { xs:0, sm:0, me:0, la:0, xl:0 }, lN : { xs:0, sm:0, me:0, la:0, xl:0 } }
16806 };
16807 tn = FlickrRetrieveImages(tn, item.primary_photo_extras, 'l1' );
16808 tn = FlickrRetrieveImages(tn, item.primary_photo_extras, 'lN' );
16809 newItem.thumbs = tn;
16810
16811 // post-process callback
16812 var fu = G.O.fnProcessData;
16813 if( fu !== null ) {
16814 typeof fu == 'function' ? fu(newItem, 'flickr', item) : window[fu](newItem, 'flickr', item);
16815 }
16816
16817 }
16818 });
16819
16820 G.I[albumIdx].contentIsLoaded=true;
16821 }
16822
16823 function FlickrRetrieveImages(tn, item, level ) {
16824
16825 var sf=1;
16826 if( G.tn.opt[level].crop === true ) {
16827 sf=G.O.thumbnailCropScaleFactor;
16828 }
16829
16830
16831 var sizes=['xs','sm','me','la','xl'];
16832 for( var i=0; i<sizes.length; i++ ) {
16833 if( G.tn.settings.width[level][sizes[i]] == 'auto' || G.tn.settings.width[level][sizes[i]] == '' ) {
16834 let sdir='height_';
16835 let tsize=Math.ceil( G.tn.settings.height[level][sizes[i]] * G.tn.scale * sf * G.tn.settings.mosaic[level+'Factor']['h'][sizes[i]] );
16836 let one=FlickrRetrieveOneImage(sdir, tsize, item );
16837 tn.url[level][sizes[i]]=one.url;
16838 tn.width[level][sizes[i]]=one.width;
16839 tn.height[level][sizes[i]]=one.height;
16840 }
16841 else
16842 if( G.tn.settings.height[level][sizes[i]] == 'auto' || G.tn.settings.height[level][sizes[i]] == '' ) {
16843 let sdir='width_';
16844 let tsize=Math.ceil( G.tn.settings.width[level][sizes[i]] * G.tn.scale * sf * G.tn.settings.mosaic[level+'Factor']['w'][sizes[i]] );
16845 let one=FlickrRetrieveOneImage(sdir, tsize, item );
16846 tn.url[level][sizes[i]]=one.url;
16847 tn.width[level][sizes[i]]=one.width;
16848 tn.height[level][sizes[i]]=one.height;
16849 }
16850 else {
16851 let sdir='height_';
16852 let tsize=Math.ceil( G.tn.settings.height[level][sizes[i]] * G.tn.scale * sf * G.tn.settings.mosaic[level+'Factor']['h'][sizes[i]] );
16853 if( G.tn.settings.width[level][sizes[i]] > G.tn.settings.height[level][sizes[i]] ) {
16854 sdir='width_';
16855 tsize=Math.ceil( G.tn.settings.width[level][sizes[i]] * G.tn.scale * sf * G.tn.settings.mosaic[level+'Factor']['w'][sizes[i]] );
16856 }
16857 let one=FlickrRetrieveOneImage(sdir, tsize, item );
16858 tn.url[level][sizes[i]]=one.url;
16859 tn.width[level][sizes[i]]=one.width;
16860 tn.height[level][sizes[i]]=one.height;
16861 }
16862 }
16863 return tn;
16864 }
16865
16866 function FlickrRetrieveOneImage(sdir, tsize, item ) {
16867 var one={ url: '', width: 0, height: 0 };
16868 var tnIndex=0;
16869 for( var j=0; j < Flickr.thumbAvailableSizes.length; j++ ) {
16870 var size=item[sdir+Flickr.photoAvailableSizesStr[j]];
16871 if( size != undefined ) {
16872 tnIndex=j;
16873 if( size >= tsize ) {
16874 break;
16875 }
16876 }
16877 }
16878 var fSize=Flickr.photoAvailableSizesStr[tnIndex];
16879 one.url = item['url_'+fSize];
16880 one.width = parseInt(item['width_'+fSize]);
16881 one.height = parseInt(item['height_'+fSize]);
16882 return one;
16883 }
16884
16885 var FilterByTags = function(data, tagBlockList) {
16886 if( tagBlockList!= '' && data != undefined) {
16887 data = data.filter(function (item) {
16888 var regex = new RegExp( tagBlockList, "i");
16889 var tagsToTest = [item.tags];
16890 if ( Array.isArray(item.tags) ) {
16891 tagsToTest = item.tags;
16892 }
16893 return ! tagsToTest.some( function (x) { return regex.test(x); } );
16894 });
16895 }
16896 return data;
16897 };
16898
16899 /** @function GetHiddenAlbums */
16900 // var GetHiddenAlbums = function( hiddenAlbums, callback ){
16901 // not supported -> doesn't exit in Flickr
16902 // callback();
16903 // }
16904
16905 // -----------
16906 // Initialize thumbnail sizes
16907 function Init() {
16908 return;
16909 }
16910
16911
16912 // shortcuts to NGY2Tools functions (with context)
16913 var PreloaderDisplay = NGY2Tools.PreloaderDisplay.bind(G);
16914 var NanoAlert = NGY2Tools.NanoAlert;
16915 var GetImageTitleFromURL = NGY2Tools.GetImageTitleFromURL.bind(G);
16916 var FilterAlbumName = NGY2Tools.FilterAlbumName.bind(G);
16917 var AlbumPostProcess = NGY2Tools.AlbumPostProcess.bind(G);
16918
16919 switch( fnName ){
16920 // case 'GetHiddenAlbums':
16921 // var hiddenAlbums = arguments[2],
16922 // callback = arguments[3];
16923 // GetHiddenAlbums(hiddenAlbums, callback);
16924 // break;
16925 case 'AlbumGetContent':
16926 var albumID = arguments[2],
16927 callback = arguments[3],
16928 cbParam1 = arguments[4],
16929 cbParam2 = arguments[5];
16930 AlbumGetContent(albumID, callback, cbParam1, cbParam2);
16931 break;
16932 case 'Init':
16933 Init();
16934 break;
16935 case '':
16936 break;
16937 }
16938
16939 };
16940
16941// END FLICKR DATA SOURCE FOR NANOGALLERY2
16942// }( jQuery ));
16943}));
16944
16945
16946