UNPKG

524 kBJavaScriptView Raw
1/**!
2 * @preserve nanogallery2 - javascript image gallery
3 * Homepage: http://nanogallery2.nanostudio.org
4 * Sources: https://github.com/nanostudio-org/nanogallery2
5 *
6 * License: GPLv3 and commercial licence
7 *
8 * Requirements:
9 * - jQuery (http://www.jquery.com) - version >= 1.7.1
10 *
11 * Embeded components:
12 * - shifty (https://github.com/jeremyckahn/shifty)
13 * - imagesloaded (https://github.com/desandro/imagesloaded)
14 * - hammer.js (http://hammerjs.github.io/)
15 * - screenfull.js (https://github.com/sindresorhus/screenfull.js)
16 * Tools:
17 * - webfont generated with http://fontello.com - mainly based on Font Awesome Copyright (C) 2012 by Dave Gandy (http://fontawesome.io/)
18 * - ICO online converter: https://iconverticons.com/online/
19 */
20
21
22// ###########################################
23// ##### nanogallery2 as a JQUERY PLUGIN #####
24// ###########################################
25
26
27// Expose plugin as an AMD module if AMD loader is present:
28(function (factory) {
29 "use strict";
30 if (typeof define === 'function' && define.amd) {
31 // AMD. Register as an anonymous module.
32 define('nanogallery2', ['jquery'], factory);
33 } else if (typeof exports === 'object' && typeof require === 'function') {
34 // Browserify
35 factory(require('jquery'));
36 } else {
37 // Browser globals
38 factory(jQuery);
39 }
40}(function ($) {
41// ;(function ($) {
42 "use strict";
43
44 //##### TOOLS/HELPERS ####
45
46 // Convert color to RGB/RGBA
47 function ColorHelperToRGB( color ) {
48 var obj = document.getElementById('ngyColorHelperToRGB');
49 if (obj === null) {
50 obj = document.createElement('div');
51 obj.id = "ngyColorHelperToRGB";
52 obj.style.cssText = 'display: none; color:'+color+';';
53 document.body.appendChild(obj);
54 }
55
56 var rgb=getComputedStyle(obj).color;
57
58 // to get HEX value:
59 // var rgb = getComputedStyle(obj).color.match(/\d+/g);
60 // var r = parseInt(rgb[0]).toString(16);
61 // var g = parseInt(rgb[1]).toString(16);
62 // var b = parseInt(rgb[2]).toString(16);
63 // var hex = '#' + r + g + b;
64
65 return rgb;
66 }
67
68
69 // ##### helper for color handling
70 // - normalise RGB/RGBA/HEX format
71 // - lighten/darken color
72 // Inspired by:
73 // https://stackoverflow.com/questions/5560248/programmatically-lighten-or-darken-a-hex-color-or-rgb-and-blend-colors
74 // http://www.pimptrizkit.com/?t=20%20Shades
75 function ShadeBlendConvert (p, from, to) {
76 var rgba='';
77 if( from.toUpperCase().substring(0,5) == 'RGBA(' ) {
78 rgba='a';
79 from='rgb('+from.substring(5);
80 }
81
82 if(typeof(p)!="number"||p<-1||p>1||typeof(from)!="string"||(from[0]!='r'&&from[0]!='#')||(typeof(to)!="string"&&typeof(to)!="undefined"))return null;
83 //if(!this.sbcRip)this.sbcRip=function(d){
84 function sbcRip(d){
85 var l=d.length,RGB=new Object();
86 if(l>9){
87 d=d.split(",");
88 if(d.length<3||d.length>4)return null;
89 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;
90 }else{
91 if(l==8||l==6||l<4)return null;
92 if(l<6)d="#"+d[1]+d[1]+d[2]+d[2]+d[3]+d[3]+(l>4?d[4]+""+d[4]:"");
93 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;
94 }
95 return RGB;
96 }
97 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);
98 if(!f||!t)return null;
99 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])+")");
100 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);
101 }
102
103
104 // ##### clone a javascript object
105 function cloneJSObject( obj ) {
106 if (obj === null || typeof obj !== 'object') {
107 return obj;
108 }
109
110 var temp = obj.constructor(); // give temp the original obj's constructor
111 for (var key in obj) {
112 temp[key] = cloneJSObject(obj[key]);
113 }
114 return temp;
115 }
116
117 // get viewport coordinates and size
118 function getViewport() {
119 var $win = jQuery(window);
120 return {
121 l: $win.scrollLeft(),
122 t: $win.scrollTop(),
123 w: $win.width(),
124 h: $win.height()
125 }
126 }
127
128
129 // avoid if possible (performance issue)
130 function inViewport( $elt, threshold ) {
131 var wp=getViewport(),
132 eltOS=$elt.offset(),
133 th=$elt.outerHeight(true),
134 tw=$elt.outerWidth(true);
135 if( eltOS.top >= (wp.t-threshold)
136 && (eltOS.top+th) <= (wp.t+wp.h+threshold)
137 && eltOS.left >= (wp.l-threshold)
138 && (eltOS.left+tw) <= (wp.l+wp.w+threshold) ) {
139 return true;
140 }
141 else {
142 return false;
143 }
144 }
145
146 // avoid if possible (performance issue)
147 function inViewportVert( $elt, threshold ) {
148 var wp=getViewport(),
149 eltOS=$elt.offset(),
150 th=$elt.outerHeight(true);
151 //var tw=$elt.outerWidth(true);
152
153 if( wp.t == 0 && (eltOS.top) <= (wp.t+wp.h ) ) { return true; }
154
155 if( eltOS.top >= (wp.t)
156 && (eltOS.top+th) <= (wp.t+wp.h-threshold) ) {
157 return true;
158 }
159 else {
160 return false;
161 }
162 }
163
164
165 // set z-index to display 2 elements on top of all others
166 function set2ElementsOnTop( start, elt1, elt2 ) {
167 var highest_index = 0;
168 if( start=='' ) { start= '*'; }
169 jQuery(start).each(function() {
170 var cur = parseInt(jQuery(this).css('z-index'));
171 highest_index = cur > highest_index ? cur : highest_index;
172 });
173 highest_index++;
174 jQuery(elt2).css('z-index',highest_index+1);
175 jQuery(elt1).css('z-index',highest_index);
176 }
177
178 // set z-index to display element on top of all others
179 function setElementOnTop( start, elt ) {
180 var highest_index = 0;
181 if( start=='' ) { start= '*'; }
182 jQuery(start).each(function() {
183 var cur = parseInt(jQuery(this).css('z-index'));
184 highest_index = cur > highest_index ? cur : highest_index;
185 });
186 highest_index++;
187 jQuery(elt).css('z-index',highest_index);
188 }
189
190 // return the real type of the object
191 var toType = function( obj ) {
192 // by Angus Croll - http://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator/
193 return ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase()
194 };
195
196
197
198
199
200 $.nanogallery2 = function (elt, options) {
201 // To avoid scope issues, use '_this' instead of 'this'
202 // to reference this class from internal events and functions.
203 var _this = this;
204
205 // Access to jQuery and DOM versions of element
206 _this.$e = jQuery(elt);
207 _this.e = elt;
208
209 // Add a reverse reference to the DOM object
210 _this.$e.data('nanogallery2data', _this);
211
212 _this.init = function () {
213
214 // define these global objects only once per HTML page
215 if (typeof window.NGY2Item === 'undefined') {
216
217 window.NGY2Tools = (function () {
218
219 function NGY2Tools() {
220 var nextId = 1; // private static --> all instances
221 }
222
223 // check album name - albumList/blackList/whiteList
224 NGY2Tools.FilterAlbumName = function( title, ID ) {
225 var s=title.toUpperCase();
226 if( this.albumList.length > 0 ) {
227 for( var j=0; j < this.albumList.length; j++) {
228 if( s === this.albumList[j].toUpperCase() || ID === this.albumList[j] ) {
229 return true;
230 }
231 }
232 }
233 else {
234 var found=false;
235 if( this.whiteList !== null ) {
236 //whiteList : authorize only album cointaining one of the specified keyword in the title
237 for( var j=0; j<this.whiteList.length; j++) {
238 if( s.indexOf(this.whiteList[j]) !== -1 ) {
239 found=true;
240 }
241 }
242 if( !found ) { return false; }
243 }
244
245
246 if( this.blackList !== null ) {
247 //blackList : ignore album cointaining one of the specified keyword in the title
248 for( var j=0; j<this.blackList.length; j++) {
249 if( s.indexOf(this.blackList[j]) !== -1 ) {
250 return false;
251 }
252 }
253 }
254
255 return true;
256 }
257 };
258
259
260 /** @function nanoAlert */
261 /* Display an alert message in a specific element */
262 NGY2Tools.NanoAlert = function(context, msg, verbose) {
263 NGY2Tools.NanoConsoleLog.call(context, msg);
264 if( context.$E.conConsole != null ) {
265 context.$E.conConsole.css({visibility:'visible', minHeight:'100px'});
266 if( verbose == false ) {
267 context.$E.conConsole.append('<p>'+ msg + '</p>');
268 }
269 else {
270 context.$E.conConsole.append('<p>nanogallery2: '+msg+ ' ['+context.baseEltID+']</p>');
271 }
272 //alert('nanoGALLERY: ' + msg);
273 }
274 };
275
276
277 /** @function NanoConsoleLog */
278 /* write message to the browser console */
279 NGY2Tools.NanoConsoleLog = function(context, msg) {
280 if (window.console) { console.log('nanogallery2: ' + msg + ' ['+context.baseEltID+']'); }
281 };
282
283
284 /** @function PreloaderDisplay() */
285 /* Display/hide preloader */
286 NGY2Tools.PreloaderDisplay = function(display) {
287 if( display === true ) {
288 this.$E.conLoadingB.removeClass('nanoGalleryLBarOff').addClass('nanoGalleryLBar');
289 }
290 else {
291 this.$E.conLoadingB.removeClass('nanoGalleryLBar').addClass('nanoGalleryLBarOff');
292 }
293 };
294
295 //+ Jonas Raoni Soares Silva
296 //@ http://jsfromhell.com/array/shuffle [v1.0]
297 NGY2Tools.AreaShuffle = function (o) {
298 for (var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
299 return o;
300 };
301
302 /** @function GetImageTitleFromURL() */
303 /* retrieve filemane */
304 NGY2Tools.GetImageTitleFromURL = function( imageURL ) {
305 if( this.O.thumbnailLabel.get('title') == '%filename' ) {
306 return (imageURL.split('/').pop()).replace('_',' ');
307 }
308
309 if( this.O.thumbnailLabel.get('title') == '%filenameNoExt' ) {
310 var s=imageURL.split('/').pop();
311 return (s.split('.').shift()).replace('_',' ');
312 }
313 // return imageURL;
314 return '';
315 };
316
317
318 /** @function AlbumPostProcess() */
319 /* post process one album based on plugin general parameters --> sorting/maxItems*/
320 NGY2Tools.AlbumPostProcess = function(albumID) {
321
322 // this function can probably be optimized....
323
324 var sortOrder=this.gallerySorting[this.GOM.curNavLevel];
325 var maxItems=this.galleryMaxItems[this.GOM.curNavLevel];
326
327 if( sortOrder != '' || maxItems > 0 ) {
328
329 // copy album's items to a new array
330 var currentAlbum=this.I.filter( function( obj ) {
331 return( obj.albumID == albumID && obj.kind != 'albumUp' );
332 });
333
334 // sorting options
335 switch( sortOrder ) {
336 case 'RANDOM':
337 currentAlbum = NGY2Tools.AreaShuffle(currentAlbum);
338 break;
339 case 'REVERSED':
340 currentAlbum = currentAlbum.reverse();
341 break;
342 case 'TITLEASC':
343 currentAlbum.sort(function (a, b) {
344 return( (a.title.toUpperCase() < b.title.toUpperCase()) ? -1 : ((a.title.toUpperCase() > b.title.toUpperCase()) ? 1 : 0) );
345 });
346 break;
347 case 'TITLEDESC':
348 currentAlbum.sort(function (a, b) {
349 return( (a.title.toUpperCase() > b.title.toUpperCase()) ? -1 : ((a.title.toUpperCase() < b.title.toUpperCase()) ? 1 : 0) );
350 });
351 break;
352 }
353
354 // max Items
355 if( maxItems > 0 && currentAlbum.length > maxItems ) {
356 currentAlbum.splice(maxItems-1,currentAlbum.length-maxItems );
357 }
358
359 // remove the albums's items from the global items array
360 this.I.removeIf( function( obj ) {
361 return( obj.albumID == albumID && obj.kind != 'albumUp' );
362 });
363
364 // add the sorted items back to the album
365 this.I.push.apply(this.I, currentAlbum);
366
367 }
368 };
369
370
371 return NGY2Tools;
372 })();
373
374 // ====================
375 // ===== NGY2Item =====
376 // ====================
377 window.NGY2Item = (function() {
378 var nextId = 1; // private static --> all instances
379
380 // constructor
381 function NGY2Item( itemID ) {
382 //window.NGY2Item = function( itemID ) {
383 var ID = 0; // private
384
385 // public (this instance only)
386 if( itemID === undefined || itemID === null ) {
387 ID = nextId++;
388 }
389 else {
390 ID = itemID;
391 }
392 this.GetID = function () { return ID; };
393
394 // public
395 this.kind = ''; // 'image', 'album' or 'albumUp'
396 this.mediaKind = 'img'; // 'img', 'iframe'
397 this.mediaMarkup = '';
398 this.G = null; // pointer to global instance
399 this.title = ''; // image title
400 this.description = ''; // image description
401 this.albumID = 0; // ID of the parent album
402 this.src = ''; // full sized image URL
403 this.width = 0; // image width
404 this.height = 0; // image height
405 this.destinationURL = ''; // thumbnail destination URL --> open URL instead of displaying image
406 this.downloadURL = ''; // thumbnail download URL --> specify the image for download button
407 this.author = ''; // image/album author
408 this.left= 0; // store position to animate from old to new
409 this.top= 0;
410 this.width= 0; // store size to avoid setting width/height if not required
411 this.height= 0;
412 this.resizedContentWidth= 0; // store size of content (image) to avoid setting width/height if not required
413 this.resizedContentHeight= 0;
414 this.thumbs = { // URLs and sizes for user defined
415 url: { l1: { xs: '', sm:'', me: '', la: '', xl: '' }, lN: { xs: '', sm: '', me: '', la:'', xl: '' } },
416 width: { l1: { xs: 0, sm: 0, me: 0, la: 0 , xl: 0 }, lN: { xs: 0 , sm: 0, me: 0, la: 0, xl: 0 } },
417 height: { l1: { xs: 0, sm: 0, me: 0, la: 0 , xl: 0 }, lN: { xs: 0, sm: 0, me: 0, la: 0, xl: 0 } }
418 };
419 this.thumbnailImgRevealed = false; // thumbnail image already revealed
420 this.imageDominantColors = null; // base64 GIF
421 this.imageDominantColor = null; // HEX RGB
422 this.featured = false; // featured element
423 this.flickrThumbSizes = {}; // store URLs for all available thumbnail sizes (flickr)
424 this.picasaThumbs = null; // store URLs and sizes
425 this.hovered = false; // is the thumbnail currently hovered?
426 this.hoverInitDone = false;
427 this.contentIsLoaded = false; // album: are items already loaded?
428 this.contentLength = 0; // album: number of items (real number of items in memory)
429 this.numberItems = 0; // album: number of items (value returned by data source)
430 this.mediaNumber = 0; // media number in the album
431 this.imageCounter = 0; // number of images in an album
432 this.eltTransform = []; // store the CSS transformations
433 this.eltFilter = []; // store the CSS filters
434 this.eltEffect = []; // store data about hover effects animations
435 this.authkey = ''; // for Google Photos private (hidden) albums
436 this.paginationLastPage = 0; // for albums
437 this.paginationLastWidth = 0; // for albums
438 this.customData = {};
439 this.selected = false;
440 this.imageWidth = 0; // image natural (real) width
441 this.imageHeight = 0; // image natural (real) height
442 this.$elt = null; // pointer to the corresponding DOM element
443 this.$Elts = []; // cached pointers to the thumbnail content -> to avoid jQuery().find()
444 this.tags = []; // list of tags of the current item
445 this.albumTagList = []; // list of all the tags of the items contained in the current album
446 this.albumTagListSel = []; // list of currently selected tags (only for albums)
447 this.exif= { exposure: '', flash: '', focallength: '', fstop: '', iso: '', model: '', time: '', location: ''};
448 }
449
450 // public static
451
452 NGY2Item.Get = function( instance, ID ) {
453 var l=instance.I.length;
454 for( var i=0; i<l; i++ ) {
455 if( instance.I[i].GetID() == ID ) {
456 return instance.I[i];
457 }
458 }
459 return null;
460 };
461
462 NGY2Item.GetIdx = function( instance, ID ) {
463 var l=instance.I.length;
464 for( var i=0; i<l; i++ ) {
465 if( instance.I[i].GetID() == ID ) {
466 return i;
467 }
468 }
469 return -1;
470 };
471
472 // create new item (image, album or albumUp)
473 NGY2Item.New = function( instance, title, description, ID, albumID, kind, tags ) {
474 var album = NGY2Item.Get( instance, albumID );
475
476 if( albumID != -1 && albumID != 0 && title !='image gallery by nanogallery2 [build]' ) {
477 if( instance.O.thumbnailLevelUp && album.getContentLength(false) == 0 && instance.O.album == '' ) {
478 // add navigation thumbnail (album up)
479 var item = new NGY2Item('0');
480 instance.I.push(item);
481 album.contentLength += 1;
482 item.title = 'UP';
483 item.albumID = albumID;
484 item.kind = 'albumUp';
485 item.G = instance;
486
487 jQuery.extend( true, item.thumbs.width, instance.tn.defaultSize.width);
488 jQuery.extend( true, item.thumbs.height, instance.tn.defaultSize.height);
489 }
490 }
491
492 var item = NGY2Item.Get(instance, ID);
493 if( item === null ){
494 // create a new item (otherwise, just update the existing one)
495 item = new NGY2Item(ID);
496 instance.I.push(item);
497 if( albumID != -1 && title !='image gallery by nanogallery2 [build]' ) {
498 album.contentLength+=1;
499 }
500 }
501 item.G = instance;
502
503 item.albumID = albumID;
504 item.kind = kind;
505 if( kind == 'image' ) {
506 album.imageCounter += 1;
507 item.mediaNumber = album.imageCounter;
508 }
509
510 // check keyword to find features images/albums
511 var kw = instance.O.thumbnailFeaturedKeyword;
512 if( kw != '' ) {
513 // check if item featured based on a keyword in the title or in the description
514 kw = kw.toUpperCase();
515 var p = title.toUpperCase().indexOf(kw);
516 if( p > -1) {
517 item.featured = true;
518 // remove keyword case unsensitive
519 title = title.substring(0, p) + title.substring(p+kw.length, title.length);
520 }
521 p = description.toUpperCase().indexOf(kw);
522 if( p > -1) {
523 item.featured=true;
524 // remove keyword case unsensitive
525 description=description.substring(0, p) + description.substring(p + kw.length, description.length);
526 }
527 }
528
529 // TAGS
530 // if( instance.galleryFilterTags.Get() != false ) {
531 // if( instance.galleryFilterTags.Get() == true ) {
532 // if( tags != '' && tags != undefined ) {
533 // use set tags
534 // item.setTags(tags.split(' '));
535 // }
536 // }
537 // else {
538 // extract tags starting with # (in title)
539 if( typeof instance.galleryFilterTags.Get() == 'string' ) {
540 switch( instance.galleryFilterTags.Get().toUpperCase() ) {
541 case 'TITLE':
542 var re = /(?:^|\W)#(\w+)(?!\w)/g, match, matches = [];
543 var tags = "";
544 while (match = re.exec(title)) {
545 matches.push(match[1].replace(/^\s*|\s*$/, '')); //trim trailing/leading whitespace
546 }
547 item.setTags(matches); //tags;
548 title = title.split('#').join(''); //replaceall
549 break;
550 case 'DESCRIPTION':
551 var re = /(?:^|\W)#(\w+)(?!\w)/g, match, matches = [];
552 var tags = "";
553 while (match = re.exec(description)) {
554 matches.push(match[1].replace(/^\s*|\s*$/, '')); //trim trailing/leading whitespace
555 }
556 item.setTags(matches); //tags;
557 description = description.split('#').join(''); //replaceall
558 break;
559 }
560 }
561 else {
562 if( tags != '' && tags != undefined ) {
563 // use set tags
564 item.setTags(tags.split(' '));
565 }
566 }
567 // }
568 // }
569
570 // set (maybe modified) fields title and description
571 item.title = escapeHtml(instance, title);
572 item.description = escapeHtml(instance, description);
573 return item;
574 };
575
576 // function to avoid XSS issue - Cross Site Scripting
577 // original: https://github.com/janl/mustache.js/blob/master/mustache.js#L55
578 var entityMap = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;', '/': '&#x2F;', '`': '&#x60;', '=': '&#x3D;' };
579 function escapeHtml (instance, string) {
580 if( instance.O.allowHTMLinData == true ) {
581 return string;
582 }
583 else {
584 return String(string).replace(/[&<>"'`=\/]/g, function fromEntityMap (s) {
585 return entityMap[s];
586 });
587 }
588 }
589
590
591 NGY2Item.get_nextId = function () {
592 return nextId;
593 };
594
595 //=== public (shared across instances)
596
597 //--- cached sub elements
598 NGY2Item.prototype.$getElt = function( elt, forceRefresh ) {
599 if( this.$elt == null ) { return null; }
600 if( this.$Elts[elt] !== undefined && !forceRefresh == true ) {
601 return this.$Elts[elt];
602 }
603 else {
604 if( elt == '.nGY2GThumbnail' ) {
605 this.$Elts[elt]=this.$elt;
606 }
607 else {
608 this.$Elts[elt]=this.$elt.find(elt);
609 }
610 return this.$Elts[elt];
611 }
612 };
613
614 // remove one element (in DOM and in cache)
615 NGY2Item.prototype.removeElt = function( elt ) {
616 if( this.$elt == null ) { return; }
617 if( this.$Elts[elt] == undefined) { return; }
618 this.$Elts[elt].remove();
619 var index = this.$Elts.indexOf(elt);
620 this.$Elts.splice(index, 1);
621 };
622
623 //--- returns the album containing the item
624 NGY2Item.prototype.album = function() {
625 return this.G.I[NGY2Item.GetIdx(this.G, this.albumID)];
626 };
627
628 //--- viewer - transition can be disabled per media kind
629 NGY2Item.prototype.mediaTransition = function( ) {
630 if( this.G.O.viewerTransitionMediaKind.indexOf( this.mediaKind ) > -1 ) {
631 return true;
632 }
633 return false;
634 };
635
636 //--- Returns Thumbnail image (depending of the screen resolution)
637 NGY2Item.prototype.thumbImg = function () {
638 var tnImg = { src: '', width: 0, height: 0 };
639
640 if( this.title == 'image gallery by nanogallery2 [build]' ) {
641 tnImg.src = this.G.emptyGif;
642 tnImg.url = this.G.emptyGif;
643 return tnImg;
644 }
645 tnImg.src = this.thumbs.url[this.G.GOM.curNavLevel][this.G.GOM.curWidth];
646 tnImg.width = this.thumbs.width[this.G.GOM.curNavLevel][this.G.GOM.curWidth];
647 tnImg.height = this.thumbs.height[this.G.GOM.curNavLevel][this.G.GOM.curWidth];
648 return tnImg;
649 };
650
651 //--- Set tags to items and add these tags to the album
652 NGY2Item.prototype.setTags = function( tags ) {
653 if( tags.length > 0 ) {
654 this.tags = tags;
655 var lstTags = this.album().albumTagList;
656 for( var i = 0; i < tags.length; i++ ) {
657 var tfound = false;
658 for( var j = 0; j < lstTags.length; j++ ) {
659 if( tags[i].toUpperCase() == lstTags[j].toUpperCase() ) {
660 tfound = true;
661 }
662 }
663 if( tfound == false) {
664 this.album().albumTagList.push(tags[i])
665 this.album().albumTagListSel.push(tags[i])
666 }
667 }
668 }
669 };
670
671 //--- check if 1 of current item's tags is selected (tag filter)
672 NGY2Item.prototype.checkTagFilter = function() {
673 if( this.G.galleryFilterTags.Get() != false && this.album().albumTagList.length > 0 ) {
674 if( this.G.O.thumbnailLevelUp && this.kind=='albumUp' ) {
675 return true;
676 }
677 var found = false;
678 var lstTags = this.album().albumTagListSel;
679 for( var i = 0; i < this.tags.length; i++ ) {
680 for( var j = 0; j < lstTags.length; j++ ) {
681 if( this.tags[i].toUpperCase() == lstTags[j].toUpperCase() ) {
682 found = true;
683 break;
684 }
685 }
686 }
687 return found;
688 }
689 else
690 return true;
691 };
692
693 //--- check if 1 of current item's tags is found using API search
694 NGY2Item.prototype.isSearchTagFound = function() {
695 if( this.G.GOM.albumSearchTags == '' ) { return true; }
696 if( this.G.O.thumbnailLevelUp && this.kind == 'albumUp' ) { return true; }
697
698 //var lstTags=this.album().albumTagListSel;
699 for( var i = 0; i < this.tags.length; i++ ) {
700 if( this.tags[i].toUpperCase().indexOf( this.G.GOM.albumSearchTags ) >= 0 ) {
701 return true;
702 }
703 }
704 return false;
705 };
706
707 //--- set the URL of the media to display in the viewer
708 //--- markup is defined for images
709 NGY2Item.prototype.setMediaURL = function( url, mediaKind ) {
710 this.src = url;
711 this.mediaKind = mediaKind;
712 if( mediaKind == 'img' ) {
713 this.mediaMarkup = '<img class="nGY2ViewerMedia" src="' + url + '" alt=" " itemprop="contentURL">';
714 }
715 };
716
717
718 //--- check if current item can be displayed
719 NGY2Item.prototype.isToDisplay = function( albumID ) {
720 return this.albumID == albumID && this.checkTagFilter() && this.isSearchFound() && this.isSearchTagFound();
721 };
722
723
724
725 //--- returns the number of items of the current album
726 //--- count using tags filter
727 NGY2Item.prototype.getContentLength = function( filterTags ) {
728 if( filterTags == false || this.albumTagList.length == 0 || this.G.galleryFilterTags.Get() == false ) {
729 return this.contentLength;
730 }
731 else {
732 var l = this.G.I.length;
733 var cnt = 0;
734 var albumID = this.GetID();
735 for( var idx = 0; idx < l; idx++ ) {
736 var item = this.G.I[idx];
737 if( item.isToDisplay(albumID) ) {
738 cnt++;
739 }
740 }
741 return cnt;
742 }
743 };
744
745 NGY2Item.prototype.isSearchFound = function() {
746 if( this.G.GOM.albumSearch != '' ) {
747 if( this.title.toUpperCase().indexOf( this.G.GOM.albumSearch ) == -1 ) {
748 return false;
749 }
750 }
751 return true;
752 }
753
754
755 //--- for future use...
756 NGY2Item.prototype.responsiveURL = function () {
757 var url = '';
758 switch(this.G.O.kind) {
759 case '':
760 url = this.src;
761 break;
762 case 'flickr':
763 url = this.src;
764 break;
765 case 'picasa':
766 case 'google':
767 case 'google2':
768 default:
769 url = this.src;
770 break;
771 }
772 return url;
773 };
774
775
776 //--- Reveal the thumbnail image with animation on opacity
777 NGY2Item.prototype.ThumbnailImageReveal = function () {
778
779 if( this.thumbnailImgRevealed == false ) {
780 this.thumbnailImgRevealed = true;
781 var tweenable = new NGTweenable();
782 tweenable.tween({
783 from: { opacity: 0 },
784 to: { opacity: 1 },
785 attachment: { item: this },
786 delay: 30,
787 duration: 400,
788 easing: 'easeOutQuart',
789 step: function (state, att) {
790 var $e=att.item.$getElt('.nGY2TnImg');
791 if( $e != null ) {
792 $e.css( state );
793 }
794 }
795 });
796 }
797 };
798
799
800 // In case of thumbnails with stacks - apply a percent to a value which include a unit
801 function ValueApplyPercent( str, percent ) {
802 str=String(str);
803 if( str === '0' || percent == 1 ) { return str; }
804 var n = Number(str.replace(/[a-zA-Z]/g, ''));
805 var ar = str.match(/([^\-0-9\.]+)/g);
806 var a = '';
807 if( ar != null && ar.length > 0 ) {
808 a = ar.join();
809 }
810
811 if( isNaN(n) || n == 0 ) {
812 return str;
813 }
814
815 n = n * percent;
816 return n + a;
817 }
818
819 //--- 2D/3D CSS transform - apply the cached value to element
820 NGY2Item.prototype.CSSTransformApply = function ( eltClass ) {
821 var obj = this.eltTransform[eltClass];
822
823 if( eltClass == '.nGY2GThumbnail' ) {
824 // thumbnail
825 var nbStacks = obj.$elt.length-1;
826 var pTranslateX = 1;
827 var pTranslateY = 1;
828 var pTranslateZ = 1;
829 var pTranslate = 1;
830 var pRotateX = 1;
831 var pRotateY = 1;
832 var pRotateZ = 1;
833 var pRotate = 1;
834 var pScale = 1;
835 for( var n = nbStacks; n >= 0; n-- ) {
836 // units must be given with
837 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) + ')';
838 if( !(this.G.IE <= 9) && !this.G.isGingerbread ) {
839 v += ' rotateX(' + ValueApplyPercent(obj.rotateX,pRotateX) + ') rotateY(' + ValueApplyPercent(obj.rotateY,pRotateY) + ') rotateZ(' + ValueApplyPercent(obj.rotateZ,pRotateZ) + ') rotate(' + ValueApplyPercent(obj.rotate,pRotate) + ')';
840 }
841 else {
842 v += ' rotate(' + ValueApplyPercent(obj.rotateZ,pRotateZ) + ')';
843 }
844 obj.$elt[n].style[this.G.CSStransformName] = v;
845
846 if( nbStacks > 0 ) {
847 // apply a percent to the stack elements
848 pTranslateX -= this.G.tn.opt.Get('stacksTranslateX');
849 pTranslateY -= this.G.tn.opt.Get('stacksTranslateY');
850 pTranslateZ -= this.G.tn.opt.Get('stacksTranslateZ');
851 pRotateX -= this.G.tn.opt.Get('stacksRotateX');
852 pRotateY -= this.G.tn.opt.Get('stacksRotateY');
853 pRotateZ -= this.G.tn.opt.Get('stacksRotateZ');
854 pScale -= this.G.tn.opt.Get('stacksScale');
855 }
856 }
857 }
858 else {
859 // thumbnail sub element
860 if( obj.$elt != null ) {
861 for( var n = 0; n < obj.$elt.length; n++ ) {
862 if( obj.$elt[n] != undefined ) {
863 // units must be given with
864 var v = 'translateX(' + obj.translateX + ') translateY(' + obj.translateY + ') translateZ(' + obj.translateY + ') scale(' + obj.scale + ') translate(' + obj.translate + ')';
865 if( !(this.G.IE <= 9) && !this.G.isGingerbread ) {
866 v += ' rotateX(' + obj.rotateX + ') rotateY(' + obj.rotateY + ') rotateZ(' + obj.rotateZ + ') rotate(' + obj.rotate + ')';
867 }
868 else {
869 v += ' rotate(' + obj.rotateZ + ')';
870 }
871 obj.$elt[n].style[this.G.CSStransformName] = v;
872 }
873 }
874 }
875 }
876 };
877
878 //--- 2D/3D CSS transform - set a value in cache
879 NGY2Item.prototype.CSSTransformSet = function ( eltClass, transform, value, forceRefresh ) {
880 if( this.eltTransform[eltClass] == undefined ) {
881 this.eltTransform[eltClass] = { translateX: 0, translateY: 0, translateZ: 0, rotateX: 0, rotateY: 0, rotateZ: 0, scale: 1, translate: '0px,0px', rotate: 0 };
882 this.eltTransform[eltClass].$elt = this.$getElt(eltClass);
883 }
884 this.eltTransform[eltClass][transform] = value;
885 if( forceRefresh === true ) {
886 this.eltTransform[eltClass].$elt = this.$getElt(eltClass, true);
887 }
888 };
889
890 //--- CSS Filters - apply the cached value to element
891 NGY2Item.prototype.CSSFilterApply = function ( eltClass ) {
892 var obj = this.eltFilter[eltClass];
893 var v = 'blur(' + obj.blur + ') brightness(' + obj.brightness + ') grayscale(' + obj.grayscale + ') sepia(' + obj.sepia + ') contrast(' + obj.contrast + ') opacity(' + obj.opacity + ') saturate(' + obj.saturate + ')';
894 if( obj.$elt != null ) {
895 for( var n = 0; n < obj.$elt.length; n++ ) {
896 if( obj.$elt[n] != undefined ) {
897 obj.$elt[n].style.WebkitFilter = v;
898 obj.$elt[n].style.filter = v;
899 }
900 }
901 }
902 };
903
904 //--- CSS Filters - set a value in cache
905 NGY2Item.prototype.CSSFilterSet = function ( eltClass, filter, value, forceRefresh ) {
906 if( this.eltFilter[eltClass] == undefined ) {
907 this.eltFilter[eltClass] = { blur: 0, brightness: '100%', grayscale: '0%', sepia: '0%', contrast: '100%', opacity: '100%', saturate: '100%' };
908 this.eltFilter[eltClass].$elt = this.$getElt(eltClass);
909 }
910 this.eltFilter[eltClass][filter] = value;
911 if( forceRefresh === true ) {
912 this.eltTransform[eltClass].$elt = this.$getElt(eltClass, true);
913 }
914 };
915
916 //--- thumbnail hover animation
917 NGY2Item.prototype.animate = function ( effect, delay, hoverIn ) {
918 if( this.$getElt() == null ) { return; }
919
920 var context = {};
921 context.G = this.G;
922 context.item = this;
923 context.effect = effect;
924 context.hoverIn = hoverIn;
925 context.cssKind = '';
926 if( hoverIn ) {
927 // HOVER IN
928
929 if( this.eltEffect[effect.element] == undefined ) {
930 this.eltEffect[effect.element] = [];
931 }
932 if( this.eltEffect[effect.element][effect.type] == undefined ) {
933 this.eltEffect[effect.element][effect.type] = { initialValue: 0, lastValue: 0 };
934 }
935 if( effect.firstKeyframe ) {
936 // store initial and current value -> for use in the back animation
937 this.eltEffect[effect.element][effect.type] = { initialValue: effect.from, lastValue: effect.from};
938 }
939
940 context.animeFrom = effect.from;
941 context.animeTo = effect.to;
942 context.animeDuration = parseInt(effect.duration);
943 context.animeDelay = 30 + parseInt(effect.delay + delay); // 30ms is a default delay to avoid conflict with other initializations
944 context.animeEasing = effect.easing;
945 }
946 else {
947 // HOVER OUT
948 if( effect.firstKeyframe ) {
949 context.animeFrom = this.eltEffect[effect.element][effect.type].lastValue;
950 context.animeTo = this.eltEffect[effect.element][effect.type].initialValue;
951 // context.animeTo=effect.from;
952 }
953 else {
954 // context.animeFrom=effect.from;
955 context.animeFrom = this.eltEffect[effect.element][effect.type].lastValue;
956 context.animeTo = this.eltEffect[effect.element][effect.type].initialValue;
957 // context.animeTo=effect.to;
958
959 }
960
961 context.animeDuration = parseInt(effect.durationBack);
962 context.animeDelay = 30 + parseInt(effect.delayBack + delay); // 30ms is a default delay to avoid conflict with other initializations
963 context.animeEasing = effect.easingBack;
964 }
965
966
967 // detect if animation on CSS transform
968 var transform=['translateX', 'translateY', 'translateZ', 'scale', 'rotateX', 'rotateY', 'rotateZ'];
969 for( var i = 0; i < transform.length; i++ ) {
970 if( effect.type == transform[i] ) {
971 context.cssKind = 'transform';
972 break;
973 }
974 }
975
976 // detect if animation on CSS filter
977 var filter=['blur', 'brightness', 'grayscale', 'sepia', 'contrast', 'opacity', 'saturate'];
978 for( var i = 0; i < filter.length; i++ ) {
979 if( effect.type == filter[i] ) {
980 context.cssKind = 'filter';
981 break;
982 }
983 }
984 // handle some special cases
985 if( hoverIn && effect.element == '.nGY2GThumbnail' && ( effect.type == 'scale' || effect.type == 'rotateX') ) {
986 this.G.GOM.lastZIndex++;
987 this.$getElt(effect.element).css('z-index', this.G.GOM.lastZIndex);
988 // setElementOnTop(this.G.$E.base, this.$getElt(effect.element) );
989 }
990
991 // animation
992 var tweenable = new NGTweenable();
993 context.tweenable=tweenable;
994 tweenable.tween({
995 attachment: context,
996 from: { 'v': context.animeFrom },
997 to: { 'v': context.animeTo },
998 duration: context.animeDuration, //parseInt(effect.duration),
999 delay: context.animeDelay, //parseInt(effect.delay),
1000 easing: context.animeEasing, //'easeOutQuart',
1001
1002 step: function (state, att) {
1003 if( att.item.$getElt() == null ) {
1004 // the thumbnail may be destroyed since the start of the animation
1005 att.tweenable.stop(false);
1006 // att.tweenable.dispose();
1007 return;
1008 }
1009 if( att.hoverIn && !att.item.hovered ) {
1010 // thumbnail no more hovered
1011 att.tweenable.stop(false);
1012 // att.tweenable.dispose();
1013 return;
1014 }
1015
1016 if( att.G.VOM.viewerDisplayed ) {
1017 att.tweenable.stop(false);
1018 // att.tweenable.dispose();
1019 return;
1020 }
1021
1022 // test if in delay phase
1023 if( state.v == att.animeFrom ) { return; }
1024
1025 switch( att.cssKind ) {
1026 case 'transform':
1027 att.item.CSSTransformSet(att.effect.element, att.effect.type, state.v);
1028 att.item.CSSTransformApply( att.effect.element );
1029 break;
1030 case 'filter':
1031 att.item.CSSFilterSet(att.effect.element, att.effect.type, state.v);
1032 att.item.CSSFilterApply( att.effect.element );
1033 break;
1034 default:
1035 var v=state.v;
1036 if( state.v.substring(0,4) == 'rgb(' || state.v.substring(0,5) == 'rgba(' ) {
1037 // to remove values after the dot (not supported by RGB/RGBA)
1038 // v=ngtinycolor(state.v).toRgbString();
1039 v = ShadeBlendConvert(0, v);
1040 }
1041 att.item.$getElt( att.effect.element ).css( att.effect.type, v );
1042 break;
1043 }
1044 if( hoverIn ) {
1045 // store value for back animation
1046 att.item.eltEffect[att.effect.element][att.effect.type].lastValue = state.v;
1047 }
1048 },
1049
1050 finish: function (state, att) {
1051 if( hoverIn ) {
1052 // store value for back animation
1053 att.item.eltEffect[att.effect.element][att.effect.type].lastValue = state.v;
1054 }
1055
1056 if( att.item.$getElt() == null ) {
1057 // the thumbnail may be destroyed since the start of the animation
1058 return;
1059 }
1060 if( att.hoverIn && !att.item.hovered ) {
1061 // thumbnail no more hovered
1062 return;
1063 }
1064
1065 if( att.G.VOM.viewerDisplayed ) {
1066 return;
1067 }
1068
1069 switch( att.cssKind ) {
1070 case 'transform':
1071 att.item.CSSTransformSet(att.effect.element, att.effect.type, att.animeTo);
1072 att.item.CSSTransformApply(att.effect.element);
1073 break;
1074 case 'filter':
1075 att.item.CSSFilterSet(att.effect.element, att.effect.type, att.animeTo);
1076 att.item.CSSFilterApply(att.effect.element);
1077 break;
1078 default:
1079 att.item.$getElt(att.effect.element).css(att.effect.type, att.animeTo);
1080 break;
1081 }
1082 }
1083 });
1084 };
1085
1086 return NGY2Item;
1087 })();
1088
1089 }
1090
1091 _this.options = jQuery.extend(true, {}, jQuery.nanogallery2.defaultOptions, options);
1092 // Initialization code
1093 _this.nG2=null;
1094 _this.nG2= new nanoGALLERY2();
1095 _this.nG2.initiateGallery2(_this.e, _this.options );
1096
1097 };
1098
1099 // PUBLIC EXPOSED METHODS
1100 _this.test = function() {
1101 //alert('test');
1102 // console.dir(_this.nG.G.I.length);
1103 // console.dir(_this.nG);
1104 //privateTest();
1105 }
1106
1107
1108 // Run initializer
1109 _this.init();
1110 };
1111
1112 jQuery.nanogallery2.defaultOptions = {
1113 kind : '',
1114 userID : '',
1115 photoset : '',
1116 album: '',
1117 blackList : 'scrapbook|profil|auto backup',
1118 whiteList : '',
1119 albumList : '',
1120 albumList2 : null,
1121 RTL : false,
1122 poogleplusUseUrlCrossDomain : true,
1123 flickrSkipOriginal : true,
1124 breadcrumbAutoHideTopLevel : true,
1125 displayBreadcrumb : true,
1126 breadcrumbOnlyCurrentLevel : true,
1127 breadcrumbHideIcons : true,
1128 theme : 'nGY2',
1129 galleryTheme : 'dark',
1130 viewerTheme : 'dark',
1131 items : null,
1132 itemsBaseURL : '',
1133 thumbnailSelectable : false,
1134 dataProvider: '',
1135 dataCharset: 'Latin',
1136 allowHTMLinData: false,
1137 locationHash : true,
1138 slideshowDelay : 3000,
1139 slideshowAutoStart : false,
1140
1141 debugMode: false,
1142
1143 galleryDisplayMoreStep : 2,
1144 galleryDisplayMode : 'fullContent',
1145 galleryL1DisplayMode : null,
1146 galleryPaginationMode : 'rectangles', // 'dots', 'rectangles', 'numbers'
1147 // galleryThumbnailsDisplayDelay : 2000,
1148 galleryMaxRows : 2,
1149 galleryL1MaxRows : null,
1150 galleryLastRowFull: false,
1151 galleryLayoutEngine : 'default',
1152 paginationSwipe: true,
1153 paginationVisiblePages : 10,
1154 // paginationSwipeSensibilityVert : 10,
1155 galleryFilterTags : false, // possible values: false, true, 'title', 'description'
1156 galleryL1FilterTags : null, // possible values: false, true, 'title', 'description'
1157 galleryMaxItems : 0, // maximum number of items per album --> only flickr, google+, nano_photos_provider2
1158 galleryL1MaxItems : null, // maximum number of items per gallery page --> only flickr, google+, nano_photos_provider2
1159 gallerySorting : '',
1160 galleryL1Sorting : null,
1161 galleryDisplayTransition : 'none',
1162 galleryL1DisplayTransition : null,
1163 galleryDisplayTransitionDuration : 1000,
1164 galleryL1DisplayTransitionDuration : null,
1165 galleryResizeAnimation : true,
1166 galleryRenderDelay : 60,
1167
1168 thumbnailCrop : true,
1169 thumbnailL1Crop : null,
1170 thumbnailCropScaleFactor : 1.5,
1171 thumbnailLevelUp : false,
1172 thumbnailAlignment : 'fillWidth',
1173 thumbnailWidth : 300,
1174 thumbnailL1Width : null,
1175 thumbnailHeight : 200,
1176 thumbnailL1Height : null,
1177 thumbnailBaseGridHeight : 0,
1178 thumbnailL1BaseGridHeight : null,
1179 thumbnailGutterWidth : 2,
1180 thumbnailL1GutterWidth : null,
1181 thumbnailGutterHeight : 2,
1182 thumbnailL1GutterHeight : null,
1183 thumbnailBorderVertical : 2,
1184 thumbnailBorderHorizontal : 2,
1185 thumbnailFeaturedKeyword : '*featured',
1186 thumbnailAlbumDisplayImage : false,
1187 thumbnailHoverEffect2 : 'toolsAppear',
1188 thumbnailBuildInit2 : '',
1189 thumbnailStacks : 0,
1190 thumbnailL1Stacks : null,
1191 thumbnailStacksTranslateX : 0,
1192 thumbnailL1StacksTranslateX : null,
1193 thumbnailStacksTranslateY : 0,
1194 thumbnailL1StacksTranslateY : null,
1195 thumbnailStacksTranslateZ : 0,
1196 thumbnailL1StacksTranslateZ : null,
1197 thumbnailStacksRotateX : 0,
1198 thumbnailL1StacksRotateX : null,
1199 thumbnailStacksRotateY : 0,
1200 thumbnailL1StacksRotateY : null,
1201 thumbnailStacksRotateZ : 0,
1202 thumbnailL1StacksRotateZ : null,
1203 thumbnailStacksScale : 0,
1204 thumbnailL1StacksScale : null,
1205 thumbnailDisplayOutsideScreen: false,
1206 thumbnailWaitImageLoaded: true,
1207 thumbnailSliderDelay: 2000,
1208 galleryBuildInit2 : '',
1209 portable : false,
1210
1211 touchAnimation : true,
1212 touchAutoOpenDelay : 0,
1213
1214 thumbnailLabel : {
1215 position : 'overImageOnBottom',
1216 align: 'center',
1217 display : true,
1218 displayDescription : false,
1219 titleMaxLength : 0,
1220 titleMultiLine : false,
1221 descriptionMaxLength : 0,
1222 descriptionMultiLine : false,
1223 hideIcons : true,
1224 title : ''
1225 },
1226
1227 thumbnailToolbarImage : { topLeft: 'select', topRight : 'featured' },
1228 thumbnailToolbarAlbum : { topLeft: 'select', topRight : 'counter' },
1229 thumbnailDisplayInterval : 15,
1230 thumbnailL1DisplayInterval : null,
1231 thumbnailDisplayTransition : 'fadeIn',
1232 thumbnailL1DisplayTransition : null,
1233 thumbnailDisplayTransitionDuration: 240,
1234 thumbnailL1DisplayTransitionDuration: null,
1235 thumbnailOpenImage : true,
1236 thumbnailOpenOriginal : false,
1237 thumbnailGlobalImageTitle : '',
1238 thumbnailGlobalAlbumTitle : '',
1239
1240 viewer : 'internal',
1241 viewerFullscreen: false,
1242 viewerDisplayLogo : false,
1243 imageTransition : 'swipe',
1244 viewerTransitionMediaKind : 'img',
1245 viewerZoom : true,
1246 viewerImageDisplay : '',
1247 openOnStart : '',
1248 viewerHideToolsDelay : 3000,
1249 viewerToolbar : {
1250 display : true,
1251 position : 'bottomOverImage',
1252 fullWidth : true,
1253 align : 'center',
1254 autoMinimize : 0,
1255 standard : 'minimizeButton,label',
1256 minimized : 'minimizeButton,label,infoButton,shareButton,downloadButton,linkOriginalButton,fullscreenButton'
1257 },
1258 viewerTools : {
1259 topLeft : 'pageCounter,playPauseButton',
1260 topRight : 'zoomButton,closeButton'
1261 },
1262
1263 breakpointSizeSM : 480,
1264 breakpointSizeME : 992,
1265 breakpointSizeLA : 1200,
1266 breakpointSizeXL : 1800,
1267
1268 fnThumbnailInit : null,
1269 fnThumbnailHoverInit : null,
1270 fnThumbnailHover : null,
1271 fnThumbnailHoverOut : null,
1272 fnThumbnailDisplayEffect : null,
1273 fnViewerInfo : null,
1274 fnImgToolbarCustInit : null,
1275 fnImgToolbarCustDisplay : null,
1276 fnImgToolbarCustClick : null,
1277 fnProcessData : null,
1278 fnThumbnailSelection : null,
1279 fnGalleryRenderStart : null,
1280 fnGalleryRenderEnd : null,
1281 fnGalleryObjectModelBuilt : null,
1282 fnGalleryLayoutApplied : null,
1283 fnThumbnailClicked : null,
1284 fnShoppingCartUpdated : null,
1285 fnThumbnailToolCustAction : null,
1286 fnThumbnailOpen : null,
1287 fnImgDisplayed : null,
1288
1289 i18n : {
1290 'breadcrumbHome' : 'Galleries', 'breadcrumbHome_FR' : 'Galeries',
1291 'thumbnailImageTitle' : '', 'thumbnailAlbumTitle' : '',
1292 'thumbnailImageDescription' : '', 'thumbnailAlbumDescription' : '',
1293 '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'
1294 },
1295 icons : {
1296 // sample for font awesome: <i style="color:#eee;" class="fa fa-search-plus"></i>
1297 thumbnailAlbum: '<i class="nGY2Icon icon-folder-empty"></i>',
1298 thumbnailImage: '<i class="nGY2Icon icon-picture"></i>',
1299 breadcrumbAlbum: '<i class="nGY2Icon icon-folder-empty"></i>',
1300 breadcrumbHome: '<i class="nGY2Icon icon-home"></i>',
1301 breadcrumbSeparator: '<i class="nGY2Icon icon-left-open"></i>',
1302 breadcrumbSeparatorRtl: '<i class="nGY2Icon icon-right-open"></i>',
1303 navigationFilterSelected: '<i style="color:#fff;" class="nGY2Icon icon-toggle-on"></i>',
1304 navigationFilterUnselected: '<i style="color:#ddd;" class="nGY2Icon icon-toggle-off"></i>',
1305 navigationFilterSelectedAll: '<i class="nGY2Icon icon-toggle-on"></i><i class="nGY2Icon icon-ok"></i>',
1306 thumbnailSelected: '<i style="color:#bff;" class="nGY2Icon icon-ok-circled"></i>',
1307 thumbnailUnselected: '<i style="color:#bff;" class="nGY2Icon icon-circle-empty"></i>',
1308 thumbnailFeatured: '<i style="color:#dd5;" class="nGY2Icon icon-star"></i>',
1309 thumbnailCounter: '<i class="nGY2Icon icon-picture"></i>',
1310 thumbnailShare: '<i class="nGY2Icon icon-ngy2_share2"></i>',
1311 thumbnailDownload: '<i class="nGY2Icon icon-ngy2_download2"></i>',
1312 thumbnailInfo: '<i class="nGY2Icon icon-ngy2_info2"></i>',
1313 thumbnailCart: '<i class="nGY2Icon icon-basket"></i>',
1314 thumbnailDisplay: '<i class="nGY2Icon icon-ngy2_zoom_in2"></i>',
1315 thumbnailCustomTool1: 'T1',
1316 thumbnailCustomTool2: 'T2',
1317 thumbnailCustomTool3: 'T3',
1318 thumbnailCustomTool4: 'T4',
1319 thumbnailCustomTool5: 'T5',
1320 thumbnailCustomTool6: 'T6',
1321 thumbnailCustomTool7: 'T7',
1322 thumbnailCustomTool8: 'T8',
1323 thumbnailCustomTool9: 'T9',
1324 thumbnailCustomTool10: 'T10',
1325 thumbnailAlbumUp: '<i style="font-size: 3em;" class="nGY2Icon icon-ngy2_chevron_up2"></i>',
1326 paginationNext: '<i class="nGY2Icon icon-right-open"></i>',
1327 paginationPrevious: '<i class="nGY2Icon icon-left-open"></i>',
1328 galleryMoreButton: '<i class="nGY2Icon icon-picture"></i> &nbsp; <i class="nGY2Icon icon-right-open"></i>',
1329 buttonClose: '<i class="nGY2Icon icon-ngy2_close2"></i>',
1330 viewerPrevious: '<i class="nGY2Icon icon-ngy2_chevron-left"></i>',
1331 viewerNext: '<i class="nGY2Icon icon-ngy2_chevron-right"></i>',
1332 viewerImgPrevious: '<i class="nGY2Icon icon-ngy2_chevron_left3"></i>',
1333 viewerImgNext: '<i class="nGY2Icon icon-ngy2_chevron_right3"></i>',
1334 viewerDownload: '<i class="nGY2Icon icon-ngy2_download2"></i>',
1335 viewerToolbarMin: '<i class="nGY2Icon icon-ellipsis-vert"></i>',
1336 viewerToolbarStd: '<i class="nGY2Icon icon-menu"></i>',
1337 viewerPlay: '<i class="nGY2Icon icon-play"></i>',
1338 viewerPause: '<i class="nGY2Icon icon-pause"></i>',
1339 viewerFullscreenOn: '<i class="nGY2Icon icon-resize-full"></i>',
1340 viewerFullscreenOff: '<i class="nGY2Icon icon-resize-small"></i>',
1341 viewerZoomIn: '<i class="nGY2Icon icon-ngy2_zoom_in2"></i>',
1342 viewerZoomOut: '<i class="nGY2Icon icon-ngy2_zoom_out2"></i>',
1343 viewerLinkOriginal: '<i class="nGY2Icon icon-ngy2_external2"></i>',
1344 viewerInfo: '<i class="nGY2Icon icon-ngy2_info2"></i>',
1345 viewerShare: '<i class="nGY2Icon icon-ngy2_share2"></i>',
1346 user: '<i class="nGY2Icon icon-user"></i>',
1347 location: '<i class="nGY2Icon icon-location"></i>',
1348 config: '<i class="nGY2Icon icon-wrench"></i>',
1349 shareFacebook: '<i style="color:#3b5998;" class="nGY2Icon icon-facebook-squared"></i>',
1350 shareTwitter: '<i style="color:#00aced;" class="nGY2Icon icon-twitter-squared"></i>',
1351 shareGooglePlus: '<i style="color:#dd4b39;" class="nGY2Icon icon-gplus-squared"></i>',
1352 shareTumblr: '<i style="color:#32506d;" class="nGY2Icon icon-tumblr-squared"></i>',
1353 sharePinterest: '<i style="color:#cb2027;" class="nGY2Icon icon-pinterest-squared"></i>',
1354 shareVK: '<i style="color:#3b5998;" class="nGY2Icon icon-vkontakte"></i>',
1355 shareMail: '<i style="color:#555;" class="nGY2Icon icon-mail-alt"></i>',
1356 viewerCustomTool1: 'T1',
1357 viewerCustomTool2: 'T2',
1358 viewerCustomTool3: 'T3',
1359 viewerCustomTool4: 'T4',
1360 viewerCustomTool5: 'T5',
1361 viewerCustomTool6: 'T6',
1362 viewerCustomTool7: 'T7',
1363 viewerCustomTool8: 'T8',
1364 viewerCustomTool9: 'T9',
1365 viewerCustomTool10: 'T10'
1366 }
1367 };
1368
1369 jQuery.fn.nanogallery2 = function (args, option, value) {
1370
1371 if( typeof jQuery(this).data('nanogallery2data') === 'undefined'){
1372 if( args == 'destroy' ) {
1373 // command to destroy but no instance yet --> exit
1374 return;
1375 }
1376
1377 return this.each( function(){
1378 (new jQuery.nanogallery2(this, args));
1379 });
1380 }
1381 else {
1382 // no options -->
1383 // This function breaks the chain, but provides some API methods
1384
1385 var nG2=$(this).data('nanogallery2data').nG2;
1386 switch(args){
1387 case 'displayItem':
1388 nG2.DisplayItem(option);
1389 break;
1390 case 'search':
1391 return( nG2.Search(option));
1392 break;
1393 case 'search2':
1394 return nG2.Search2(option, value);
1395 break;
1396 case 'search2Execute':
1397 return nG2.Search2Execute();
1398 break;
1399 case 'refresh':
1400 nG2.Refresh();
1401 break;
1402 case 'instance':
1403 return nG2;
1404 break;
1405 case 'data':
1406 nG2.data= {
1407 items: nG2.I,
1408 gallery: nG2.GOM,
1409 lightbox: nG2.VOM
1410 };
1411 return nG2.data;
1412 break;
1413 case 'reload':
1414 nG2.ReloadAlbum();
1415 return $(this);
1416 break;
1417 case 'itemsSelectedGet':
1418 return nG2.ItemsSelectedGet();
1419 break;
1420 case 'itemsSetSelectedValue':
1421 nG2.ItemsSetSelectedValue(option, value);
1422 break;
1423 case 'option':
1424 if(typeof value === 'undefined'){
1425 return nG2.Get(option);
1426 }else{
1427 nG2.Set(option,value);
1428 if( option == 'demoViewportWidth' ) {
1429 // force resize event -> for demo purposes
1430 $(window).trigger('resize');
1431 }
1432 }
1433 break;
1434 case 'destroy':
1435 nG2.Destroy();
1436 $(this).removeData('nanogallery2data');
1437 break;
1438 case 'shoppingCartGet':
1439 return nG2.shoppingCart;
1440 break;
1441 case 'shoppingCartUpdate':
1442 if( typeof value === 'undefined' || typeof option === 'undefined' ){
1443 return false;
1444 }
1445 var ID=option;
1446 var cnt=value;
1447 for( var i=0; i<nG2.shoppingCart.length; i++) {
1448 if( nG2.shoppingCart[i].ID=ID ) {
1449 nG2.shoppingCart[i].cnt=cnt;
1450 }
1451 }
1452 var fu=G.O.fnShoppingCartUpdated;
1453 if( fu !== null ) {
1454 fu == 'function' ? fu(nG2.shoppingCart) : window[fu](nG2.shoppingCart);
1455 }
1456 return nG2.shoppingCart;
1457 break;
1458 case 'shoppingCartRemove':
1459 if( typeof option === 'undefined' ){
1460 return false;
1461 }
1462 var ID=option;
1463 for( var i=0; i<nG2.shoppingCart.length; i++) {
1464 if( nG2.shoppingCart[i].ID=ID ) {
1465 nG2.shoppingCart.splice(i,1);
1466 break;
1467 }
1468 }
1469 var fu=G.O.fnShoppingCartUpdated;
1470 if( fu !== null ) {
1471 fu == 'function' ? fu(nG2.shoppingCart) : window[fu](nG2.shoppingCart);
1472 }
1473 return nG2.shoppingCart;
1474 break;
1475 }
1476 return $(this);
1477
1478 }
1479 };
1480
1481
1482 // ###############################
1483 // ##### nanogallery2 script #####
1484 // ###############################
1485
1486 /** @function nanoGALLERY2 */
1487 function nanoGALLERY2() {
1488 "use strict";
1489
1490 /**
1491 * Force reload the current album, if provided by Json
1492 */
1493 this.ReloadAlbum = function(){
1494 if( G.O.kind === '' ) {
1495 throw 'Not supported for this content source:' + G.O.kind;
1496 }
1497
1498 var albumIdx=G.GOM.albumIdx;
1499 if( albumIdx == -1 ) {
1500 throw ('Current album not found.');
1501 }
1502
1503 var albumID = G.I[albumIdx].GetID();
1504
1505 // unselect everything & remove link to album (=logical delete)
1506 var l = G.I.length;
1507 for( var i = 0; i < l ; i++ ) {
1508 var item = G.I[i];
1509 if( item.albumID == albumID ) {
1510 item.selected = false;
1511 }
1512 }
1513
1514 G.I[albumIdx].contentIsLoaded = false;
1515
1516 DisplayAlbum('-1', albumID);
1517 };
1518
1519 /**
1520 * Set one or several items selected/unselected
1521 * @param {array} items
1522 */
1523 this.ItemsSetSelectedValue = function(items, value){
1524 var l = items.length;
1525 for( var j = 0; j < l ; j++) {
1526 ThumbnailSelectionSet(items[j], value);
1527 }
1528 };
1529
1530 /**
1531 * Returns an array of selected items
1532 * @returns {Array}
1533 */
1534 this.ItemsSelectedGet = function(){
1535 var selectedItems = [];
1536 var l = G.I.length;
1537 for( var i = 0; i < l ; i++ ) {
1538 if( G.I[i].selected == true ) {
1539 selectedItems.push(G.I[i]);
1540 }
1541 }
1542 return selectedItems;
1543 };
1544
1545 /**
1546 * Returns the value of an option
1547 * @param {string} option
1548 * @returns {nanoGALLERY.G.O}
1549 */
1550 this.Get = function(option){
1551 return G.O[option];
1552 };
1553
1554 /**
1555 * Set a new value for a defined option
1556 * @param {string} option
1557 */
1558 this.Set = function(option, value){
1559 G.O[option] = value;
1560 switch( option ) {
1561 case 'thumbnailSelectable':
1562 ThumbnailSelectionClear();
1563 // refresh the displayed gallery
1564 GalleryRender( G.GOM.albumIdx );
1565 break;
1566 }
1567 };
1568
1569 /**
1570 * refresh the current gallery
1571 */
1572 this.Refresh = function() {
1573 // refresh the displayed gallery
1574 GalleryRender( G.GOM.albumIdx );
1575 };
1576
1577 /**
1578 * display one item (image or gallery)
1579 * itemID syntax:
1580 * - albumID --> display one album
1581 * - albumID/imageID --> display one image
1582 */
1583 this.DisplayItem = function( itemID ) {
1584 var IDs=parseIDs( itemID );
1585 if( IDs.imageID != '0' ) {
1586 DisplayPhoto( IDs.imageID, IDs.albumID );
1587 }
1588 else {
1589 DisplayAlbum( '-1', IDs.albumID );
1590 }
1591 };
1592
1593
1594
1595 var CountItemsToDisplay = function( gIdx ) {
1596 if( G.I[gIdx] == undefined ) { return 0; }
1597 var albumID = G.I[gIdx].GetID();
1598 var l = G.I.length;
1599 var cnt = 0;
1600 for( var idx = 0; idx < l; idx++ ) {
1601 var item = G.I[idx];
1602 if( item.isToDisplay(albumID) ) {
1603 cnt++;
1604 }
1605 }
1606 return cnt;
1607 }
1608 /**
1609 * Search in the displayed gallery (in thumbnails title)
1610 */
1611 this.Search = function( search ) {
1612 G.GOM.albumSearch = search.toUpperCase();
1613 var gIdx = G.GOM.albumIdx;
1614 GalleryRender( G.GOM.albumIdx );
1615 return CountItemsToDisplay( gIdx );
1616 };
1617
1618 /**
1619 * Search2 in title and tags - set search values
1620 */
1621 this.Search2 = function( searchTitle, searchTags ) {
1622 if( searchTitle != null && searchTitle != undefined ) {
1623 G.GOM.albumSearch=searchTitle.toUpperCase();
1624 }
1625 else {
1626 G.GOM.albumSearch='';
1627 }
1628
1629 if( searchTags != null && searchTags != undefined ) {
1630 G.GOM.albumSearchTags=searchTags.toUpperCase();
1631 }
1632 else {
1633 G.GOM.albumSearchTags = '';
1634 }
1635 return CountItemsToDisplay( G.GOM.albumIdx );
1636 };
1637 /**
1638 * Search2 - execute the search on title and tags
1639 */
1640 this.Search2Execute = function() {
1641 var gIdx=G.GOM.albumIdx;
1642 GalleryRender( G.GOM.albumIdx );
1643 return CountItemsToDisplay( gIdx );
1644 };
1645
1646
1647 /**
1648 * Destroy the current gallery
1649 */
1650 this.Destroy = function(){
1651 // alert('destroy');
1652 // var event = new Event('build');
1653 if( G.GOM.hammertime != null ) {
1654 G.GOM.hammertime.destroy();
1655 G.GOM.hammertime = null;
1656 }
1657 // G.GOM.userEvents.RemoveEvtListener();
1658 // G.GOM.userEvents=null;
1659 // G.VOM.userEvents.RemoveEvtListener();
1660 // G.VOM.userEvents=null;
1661 if( G.VOM.hammertime != null ) {
1662 G.VOM.hammertime.destroy();
1663 G.VOM.hammertime = null;
1664 }
1665 //ThumbnailHoverReInitAll();
1666
1667 // color scheme
1668 $('#ngycs_' + G.baseEltID).remove()
1669
1670 G.GOM.items = [];
1671 NGY2Item.New( G, G.i18nTranslations.breadcrumbHome, '', '0', '-1', 'album' );
1672 G.GOM.navigationBar.$newContent = null;
1673 G.$E.base.empty();
1674 G.$E.base.removeData();
1675 if( G.O.locationHash ) {
1676 jQuery(window).off('hashchange.nanogallery2.'+G.baseEltID);
1677 }
1678 jQuery(window).off('resize.nanogallery2.'+G.baseEltID);
1679 jQuery(window).off('orientationChange.nanogallery2.'+G.baseEltID);
1680 jQuery(window).off('scroll.nanogallery2.'+G.baseEltID);
1681 G.GOM.firstDisplay=false;
1682 };
1683
1684
1685
1686 // throttle()
1687 // author: underscore.js - http://underscorejs.org/docs/underscore.html
1688 // Returns a function, that, when invoked, will only be triggered at most once during a given window of time.
1689 // Normally, the throttled function will run as much as it can, without ever going more than once per wait duration;
1690 // but if you�d like to disable the execution on the leading edge, pass {leading: false}.
1691 // To disable execution on the trailing edge, ditto.
1692 var throttle = function(func, wait, options) {
1693 var context, args, result;
1694 var timeout = null;
1695 var previous = 0;
1696 if (!options) options = {};
1697 var later = function() {
1698 previous = options.leading === false ? 0 : new Date().getTime();
1699 timeout = null;
1700 result = func.apply(context, args);
1701 if (!timeout) context = args = null;
1702 };
1703 return function() {
1704 var now = new Date().getTime();
1705 if (!previous && options.leading === false) previous = now;
1706 var remaining = wait - (now - previous);
1707 context = this;
1708 args = arguments;
1709 if (remaining <= 0 || remaining > wait) {
1710 if (timeout) {
1711 clearTimeout(timeout);
1712 timeout = null;
1713 }
1714 previous = now;
1715 result = func.apply(context, args);
1716 if (!timeout) context = args = null;
1717 } else if (!timeout && options.trailing !== false) {
1718 timeout = setTimeout(later, remaining);
1719 }
1720 return result;
1721 };
1722 };
1723
1724
1725 // DEBOUNCE
1726 // author: John Hann - http://unscriptable.com/2009/03/20/debouncing-javascript-methods/
1727 // execAsap - false means executing at the end of the detection period
1728 var debounce = function (func, threshold, execAsap) {
1729 var timeout;
1730 return function debounced () {
1731 var obj = this, args = arguments;
1732 function delayed () {
1733 if (!execAsap)
1734 func.apply(obj, args);
1735 timeout = null;
1736 };
1737
1738 if (timeout)
1739 clearTimeout(timeout);
1740 else if (execAsap)
1741 func.apply(obj, args);
1742 timeout = setTimeout(delayed, threshold || 100);
1743 };
1744 }
1745
1746
1747 /*
1748 ** Global data for this nanogallery2 instance
1749 **/
1750 var G=this;
1751 G.I = []; // gallery items
1752 G.Id = []; // gallery items
1753 G.O = null; // user options
1754 G.baseEltID = null; // ID of the base element
1755 G.$E = {
1756 base: null, // base element
1757 conTnParent: null, // $g_containerThumbnailsParent
1758 conLoadingB: null, // loading bar - nanoGalleryLBarOff
1759 conConsole: null, // console for error messages
1760 conNavigationBar: null, // gallery navigation bar
1761 conTnBottom: null // container on the bottom of the gallery
1762 };
1763 G.shoppingCart = [];
1764 G.layout = { // Layout informations
1765 internal : true,
1766 engine : '',
1767 support : { rows: false },
1768 prerequisite : { imageSize: false },
1769 SetEngine: function() {
1770
1771 if( G.layout.internal ) {
1772 if( G.tn.settings.width[G.GOM.curNavLevel][G.GOM.curWidth] == 'auto' || G.tn.settings.width[G.GOM.curNavLevel][G.GOM.curWidth] == '' ) {
1773 // do not use getH() / getW() here!
1774 G.layout.engine = 'JUSTIFIED';
1775 G.layout.support.rows = true;
1776 G.layout.prerequisite.imageSize = true;
1777 return;
1778 }
1779 if( G.tn.settings.height[G.GOM.curNavLevel][G.GOM.curWidth] == 'auto' || G.tn.settings.height[G.GOM.curNavLevel][G.GOM.curWidth] == '' ) {
1780 // do not use getH() / getW() here!
1781 G.layout.engine = 'CASCADING';
1782 G.layout.support.rows = false;
1783 G.layout.prerequisite.imageSize = true;
1784 return;
1785 }
1786
1787 if( G.tn.settings.getMosaic() != null ) {
1788 G.layout.engine = 'MOSAIC';
1789 G.layout.support.rows = true;
1790 G.layout.prerequisite.imageSize = false;
1791 return;
1792 }
1793
1794 G.layout.engine='GRID';
1795 G.layout.support.rows=true;
1796 // if( G.tn.opt.Get('crop') === true ) {
1797 // G.layout.prerequisite.imageSize = true;
1798 // }
1799 // else {
1800 G.layout.prerequisite.imageSize = false;
1801 // }
1802 }
1803 }
1804 };
1805 G.galleryResizeEventEnabled = false;
1806 G.galleryMaxRows = { l1: 0, lN: 0,
1807 Get: function() {
1808 return G.galleryMaxRows[G.GOM.curNavLevel];
1809 }
1810 };
1811 G.galleryMaxItems = { l1: 0, lN: 0,
1812 Get: function() {
1813 return G.galleryMaxItems[G.GOM.curNavLevel];
1814 }
1815 };
1816 G.galleryFilterTags = { l1: 0, lN: 0,
1817 Get: function() {
1818 return G.galleryFilterTags[G.GOM.curNavLevel];
1819 }
1820 };
1821 G.galleryDisplayMode = { l1: 'FULLCONTENT', lN: 'FULLCONTENT',
1822 Get: function() {
1823 return G.galleryDisplayMode[G.GOM.curNavLevel];
1824 }
1825 };
1826 G.galleryLastRowFull = { l1: false, lN: false,
1827 Get: function() {
1828 return G.galleryLastRowFull[G.GOM.curNavLevel];
1829 }
1830 };
1831 G.gallerySorting = { l1: '', lN: '',
1832 Get: function() {
1833 return G.gallerySorting[G.GOM.curNavLevel];
1834 }
1835 };
1836 G.galleryDisplayTransition = { l1: 'none', lN: 'none',
1837 Get: function() {
1838 return G.galleryDisplayTransition[G.GOM.curNavLevel];
1839 }
1840 };
1841 G.galleryDisplayTransitionDuration = { l1: 500, lN: 500,
1842 Get: function() {
1843 return G.galleryDisplayTransitionDuration[G.GOM.curNavLevel];
1844 }
1845 };
1846 G.$currentTouchedThumbnail = null;
1847
1848 // ##### GENERAL THUMBNAILS PROPERTIES -->
1849 G.tn = {
1850 // levell specific options
1851 opt: {
1852 l1: { crop: true, stacks: 0, stacksTranslateX: 0, stacksTranslateY: 0, stacksTranslateZ: 0, stacksRotateX: 0, stacksRotateY: 0, stacksRotateZ: 0, stacksScale: 0, gutterHeight: 0, gutterWidth: 0, baseGridHeight: 0, displayTransition: 'FADEIN', displayTransitionStartVal: 0, displayTransitionEasing: 'easeOutQuart', displayTransitionDuration: 240, displayInterval: 15 },
1853 lN: { crop: true, stacks: 0, stacksTranslateX: 0, stacksTranslateY: 0, stacksTranslateZ: 0, stacksRotateX: 0, stacksRotateY: 0, stacksRotateZ: 0, stacksScale: 0, gutterHeight: 0, gutterWidth: 0, baseGridHeight: 0, displayTransition: 'FADEIN', displayTransitionStartVal: 0, displayTransitionEasing: 'easeOutQuart', displayTransitionDuration: 240, displayInterval: 15 },
1854 Get: function(opt) {
1855 return G.tn.opt[G.GOM.curNavLevel][opt];
1856 }
1857 },
1858 scale: 1, // image scale depending of the hover effect
1859 borderWidth: 0, // thumbnail container border width
1860 borderHeight: 0, // thumbnail container border height
1861 labelHeight: { // in case label on bottom, otherwise always=0
1862 l1: 0, lN: 0,
1863 get: function() {
1864 return G.tn.labelHeight[G.GOM.curNavLevel];
1865 }
1866 },
1867 defaultSize: { // default thumbnail size
1868 // label height is not included
1869 width: { l1 : { xs:0, sm:0, me:0, la:0, xl:0 }, lN : { xs:0, sm:0, me:0, la:0, xl:0 } },
1870 height: { l1 : { xs:0, sm:0, me:0, la:0, xl:0 }, lN : { xs:0, sm:0, me:0, la:0, xl:0 } },
1871 getWidth: function() {
1872 return G.tn.defaultSize.width[G.GOM.curNavLevel][G.GOM.curWidth];
1873 },
1874 getOuterWidth: function() { // width border included
1875 return G.tn.defaultSize.width[G.GOM.curNavLevel][G.GOM.curWidth]+G.tn.borderWidth*2;
1876 },
1877 getHeight: function() {
1878 return G.tn.defaultSize.height[G.GOM.curNavLevel][G.GOM.curWidth];
1879 },
1880 getOuterHeight: function() { // height, border included
1881 return G.tn.defaultSize.height[G.GOM.curNavLevel][G.GOM.curWidth]+G.tn.borderHeight*2;
1882 }
1883 },
1884 settings: { // user defined width/height of the image to display depending on the screen size
1885 width: { l1 : { xs:0, sm:0, me:0, la:0, xl:0, xsc:'u', smc:'u', mec:'u', lac:'u', xlc:'u' },
1886 lN : { xs:0, sm:0, me:0, la:0, xl:0, xsc:'u', smc:'u', mec:'u', lac:'u', xlc:'u' } },
1887 height: { l1 : { xs:0, sm:0, me:0, la:0, xl:0, xsc:'u', smc:'u', mec:'u', lac:'u', xlc:'u' },
1888 lN : { xs:0, sm:0, me:0, la:0, xl:0, xsc:'u', smc:'u', mec:'u', lac:'u', xlc:'u' } },
1889 getH: function(l, w) {
1890 var cl = (l == undefined ? G.GOM.curNavLevel : l);
1891 var cw = (w == undefined ? G.GOM.curWidth : w);
1892 if( G.layout.engine == 'MOSAIC' ) {
1893 return G.tn.settings.height[cl][cw] * G.tn.settings.mosaic[cl+'Factor']['h'][cw];
1894 }
1895 else {
1896 return G.tn.settings.height[cl][cw];
1897 }
1898 },
1899 getW: function(l, w) {
1900 var cl = (l == undefined ? G.GOM.curNavLevel : l);
1901 var cw = (w == undefined ? G.GOM.curWidth : w);
1902 if( G.layout.engine == 'MOSAIC' ) {
1903 return G.tn.settings.width[cl][cw] * G.tn.settings.mosaic[cl+'Factor']['w'][cw];
1904 }
1905 else {
1906 return G.tn.settings.width[cl][cw];
1907 // return G.tn.settings.width[G.GOM.curNavLevel][G.GOM.curWidth];
1908 }
1909 },
1910 mosaic: { l1 : { xs: null, sm: null, me: null, la: null, xl: null },
1911 lN : { xs: null, sm: null, me: null, la: null, xl: null },
1912 l1Factor : { h :{ xs: 1, sm: 1, me: 1, la: 1, xl: 1 }, w :{ xs: 1, sm: 1, me: 1, la: 1, xl: 1 }},
1913 lNFactor : { h :{ xs: 1, sm: 1, me: 1, la: 1, xl: 1 }, w :{ xs: 1, sm: 1, me: 1, la: 1, xl: 1 }}
1914 },
1915 getMosaic: function() {
1916 return G.tn.settings.mosaic[G.GOM.curNavLevel][G.GOM.curWidth];
1917 },
1918 mosaicCalcFactor: function(l, w) {
1919 // retrieve max size multiplicator
1920 var maxW = 1;
1921 var maxH = 1;
1922 for( var n = 0; n < G.tn.settings.mosaic[l][w].length; n++ ) {
1923 maxW = Math.max(maxW, G.tn.settings.mosaic[l][w][n]['w']);
1924 maxH = Math.max(maxH, G.tn.settings.mosaic[l][w][n]['h']);
1925 }
1926 G.tn.settings.mosaic[l + 'Factor']['h'][w] = maxH;
1927 G.tn.settings.mosaic[l + 'Factor']['w'][w] = maxW;
1928 }
1929 },
1930 // thumbnail hover effects
1931 hoverEffects : {
1932 std : [],
1933 level1: [],
1934 get: function() {
1935 if( G.GOM.curNavLevel == 'l1' && G.tn.hoverEffects.level1.length !== 0 ) {
1936 return G.tn.hoverEffects.level1;
1937 }
1938 else {
1939 return G.tn.hoverEffects.std;
1940 }
1941 }
1942 },
1943 // thumbnail init
1944 buildInit : {
1945 std : [],
1946 level1: [],
1947 get: function() {
1948 if( G.GOM.curNavLevel == 'l1' && G.tn.buildInit.level1.length !== 0 ) {
1949 return G.tn.buildInit.level1;
1950 }
1951 else {
1952 return G.tn.buildInit.std;
1953 }
1954 }
1955 },
1956 // thumbnail toolbars
1957 toolbar: {
1958 album : { topLeft : '', topRight: '', bottomLeft: '', bottomRight: '' },
1959 image : { topLeft : '', topRight: '', bottomLeft: '', bottomRight: '' },
1960 albumUp : { topLeft : '', topRight: '', bottomLeft: '', bottomRight: '' },
1961 get: function( item ) {
1962 return G.tn.toolbar[item.kind];
1963 },
1964 },
1965 style: {
1966 // inline CSS
1967 l1 : { label: '', title: '', desc: '' },
1968 lN : { label: '', title: '', desc: '' },
1969 getTitle : function() {
1970 return ('style="' + G.tn.style[G.GOM.curNavLevel].title + '"');
1971 },
1972 getDesc : function() {
1973 return ('style="' + G.tn.style[G.GOM.curNavLevel].desc + '"');
1974 },
1975 getLabel: function() {
1976 var s='style="'+ G.tn.style[G.GOM.curNavLevel].label;
1977 s+= (G.O.RTL ? '"direction:RTL;"' :'');
1978 s+='"';
1979 return s;
1980 }
1981 }
1982 };
1983 G.scrollTimeOut = 0;
1984 G.i18nTranslations = {'paginationPrevious':'Previous', 'paginationNext':'Next', 'breadcrumbHome':'List of Albums', 'thumbnailImageTitle':'', 'thumbnailAlbumTitle':'', 'thumbnailImageDescription':'', 'thumbnailAlbumDescription':'' };
1985 G.emptyGif = 'data:image/gif;base64,R0lGODlhEAAQAIAAAP///////yH5BAEKAAEALAAAAAAQABAAAAIOjI+py+0Po5y02ouzPgUAOw==';
1986 G.CSStransformName = FirstSupportedPropertyName(["transform", "msTransform", "MozTransform", "WebkitTransform", "OTransform"]);
1987 // G.CSSfilterName = FirstSupportedPropertyName(["filter", "WebkitFilter"]);
1988 G.CSStransformStyle = FirstSupportedPropertyName(["transformStyle", "msTransformStyle", "MozTransformStyle", "WebkitTransformStyle", "OTransformStyle"]);
1989 G.CSSperspective = FirstSupportedPropertyName(["perspective", "msPerspective", "MozPerspective", "WebkitPerspective", "OPerspective"]);
1990 G.CSSbackfaceVisibilityName = FirstSupportedPropertyName(["backfaceVisibility", "msBackfaceVisibility", "MozBackfaceVisibility", "WebkitBackfaceVisibility", "OBackfaceVisibility"]);
1991 G.CSStransitionName = FirstSupportedPropertyName(["transition", "msTransition", "MozTransition", "WebkitTransition", "OTransition"]);
1992 G.CSSanimationName = FirstSupportedPropertyName(["animation", "msAnimation", "MozAnimation", "WebkitAnimation", "OAnimation"]);
1993 G.GalleryResizeThrottled = throttle(GalleryResize, 100, {leading: false});
1994
1995 G.blackList = null; // album white list
1996 G.whiteList = null; // album black list
1997 G.albumList = []; // album list
1998 G.albumListHidden = []; // for Google Photos -> hidden albums with private key
1999 G.locationHashLastUsed = '';
2000 G.custGlobals = {};
2001 G.touchAutoOpenDelayTimerID = 0;
2002 G.i18nLang = '';
2003 G.timeLastTouchStart = 0;
2004 G.custGlobals = {};
2005 G.markupOrApiProcessed = false;
2006
2007 //------------------------
2008 //--- Gallery Object Model
2009 G.GOM = {
2010 albumIdx : -1, // index (in G.I) of the currently displayed album
2011 clipArea : { top: 0, height: 0 }, // area of the GOM to display on screen
2012 displayArea : { width: 0 , height: 0 }, // size of the GOM area (=used area, not available area)
2013 displayAreaLast : { width: 0 , height: 0 }, // previous size of the GOM area
2014 displayedMoreSteps : 0, // current number of displayed steps (moreButton mode)
2015 items: [], // current items of the GOMS
2016 $imgPreloader: [],
2017 itemsDisplayed : 0, // number of currently displayed thumbnails
2018 firstDisplay : true,
2019 firstDisplayTime : 0, // in conjunction with galleryRenderDelay
2020 navigationBar : { // content of the navigation bar (for breadcrumb and filter tags)
2021 displayed: false,
2022 $newContent: ''
2023 },
2024 cache : { // cached data
2025 viewport: null,
2026 containerOffset: null,
2027 areaWidth: 100 // available area width
2028 },
2029 nbSelected : 0, // number of selected items
2030 pagination : { currentPage: 0 }, // pagination data
2031 lastFullRow : -1, // number of the last row without holes
2032 lastDisplayedIdx: -1, // used to display the counter of not displayed items
2033 displayInterval : { from: 0, len: 0 },
2034 userEvents: null,
2035 hammertime: null,
2036 curNavLevel: 'l1', // current navigation level (l1 or LN)
2037 curWidth: 'me',
2038 albumSearch: '', // current search string -> title (used to filter the thumbnails on screen)
2039 albumSearchTags: '', // current search string -> tags
2040 lastZIndex: 0, // used to put a thumbnail on top of all others (for exemple for scale hover effect)
2041 lastRandomValue: 0,
2042 slider : { // slider on last thumbnail
2043 hostIdx: -1, // idx of the thumbnail hosting the slider
2044 hostItem: null, // item hosting the slider
2045 currentIdx: 0, // idx of the current displayed item
2046 nextIdx: 0, // idx of the next item to display in the slider
2047 timerID: 0,
2048 tween: null // tranistion tween instance
2049 },
2050 NGY2Item: function( idx ) { // returns a NGY2Item or null if it does not exist
2051 if( G.GOM.items[idx] == undefined || G.GOM.items[idx] == null ) { return null; }
2052 var i = G.GOM.items[idx].thumbnailIdx;
2053 return G.I[i];
2054 }
2055 };
2056
2057 // One GOM item (thumbnail)
2058 function GTn(index, width, height) {
2059 this.thumbnailIdx = index;
2060 this.width = 0; // thumbnail width
2061 this.height = 0; // thumbnail height
2062 this.top = 0; // position: top
2063 this.left = 0; // position: left
2064 this.row = 0; // position: row number
2065 this.imageWidth = width; // image width
2066 this.imageHeight = height; // image height
2067 this.resizedContentWidth = 0;
2068 this.resizedContentHeight = 0;
2069 this.displayed = false;
2070 this.neverDisplayed = true;
2071 this.inDisplayArea = false;
2072
2073 }
2074
2075 //------------------------
2076 //--- Viewer Object Model
2077
2078 G.VOM = {
2079 viewerDisplayed: false, // is the viewer currently displayed
2080 viewerIsFullscreen: false, // viewer in fullscreen mode
2081 infoDisplayed: false, // is the info box displayed
2082 toolbarsDisplayed: true, // the toolbars are displayed
2083 toolsHide: null,
2084 saveOverflowX: 'visible', // store the value to restore it back after viewer is closed
2085 saveOverflowY: 'visible',
2086 zoom : {
2087 posX: 0, // position to center zoom in/out
2088 posY: 0,
2089 userFactor: 1, // user zoom factor (applied to the baseZoom factor)
2090 isZooming: false
2091 },
2092 padding: { H: 0, V: 0 }, // padding for the image
2093 window: { lastWidth: 0, lastHeight: 0 },
2094 $cont: null, // viewer container
2095 $viewer: null,
2096 $toolbar: null, // viewerToolbar
2097 $toolbarTL: null, // viewer toolbar on top left
2098 $toolbarTR: null, // viewer toolbar on top right
2099 $content: null, // viewer content
2100
2101 $mediaPrevious: null, // previous image
2102 $mediaCurrent: null, // current image
2103 $mediaNext: null, // next image
2104 toolbarMode: 'std', // current toolbar mode (standard, minimized)
2105 playSlideshow : false, // slide show mode status
2106 playSlideshowTimerID: 0, // slideshow mode time
2107 slideshowDelay: 3000, // slideshow mode - delay before next image
2108 albumID: -1,
2109 currItemIdx: -1,
2110 viewerMediaIsChanged: false, // media display is currently modified
2111 items: [], // current list of images to be managed by the viewer
2112 NGY2Item: function( n ) { // returns a NGY2Item
2113 switch( n ) {
2114 case -1: // previous
2115 var idx=this.IdxPrevious();
2116 return G.I[this.items[idx].ngy2ItemIdx]
2117 break;
2118 case 1: // next
2119 var idx=this.IdxNext();
2120 return G.I[this.items[idx].ngy2ItemIdx]
2121 break;
2122 case 0: // current
2123 default:
2124 return G.I[this.items[G.VOM.currItemIdx].ngy2ItemIdx];
2125 break;
2126 }
2127 },
2128 IdxNext: function() {
2129 var n=0;
2130 if( G.VOM.currItemIdx != G.VOM.items.length-1 ) {
2131 n=G.VOM.currItemIdx+1;
2132 }
2133 return n;
2134 },
2135 IdxPrevious: function() {
2136 var n=G.VOM.currItemIdx-1;
2137 if( G.VOM.currItemIdx == 0 ) {
2138 n=G.VOM.items.length-1;
2139 }
2140 return n;
2141 },
2142 userEvents: null, // user events management
2143 hammertime: null, // hammer.js manager
2144 swipePosX: 0, // current horizontal swip position
2145 panPosX: 0, // position for manual pan
2146 panPosY: 0,
2147 viewerTheme: '',
2148 timeImgChanged: 0,
2149 ImageLoader: {
2150 // fires a callback when image size is know (during download)
2151 // inspired by ROB - http://stackoverflow.com/users/226507/rob
2152 maxChecks: 1000,
2153 list: [],
2154 intervalHandle : null,
2155
2156 loadImage : function (callback, ngitem) {
2157 if( ngitem.mediaKind != 'img' ) { return; }
2158 var img = new Image ();
2159 img.src = ngitem.responsiveURL();
2160 if (img.width && img.height) {
2161 callback (img.width, img.height, ngitem, 0);
2162 }
2163 else {
2164 var obj = {image: img, url: ngitem.responsiveURL(), ngitem: ngitem, callback: callback, checks: 1};
2165 var i;
2166 for (i=0; i < this.list.length; i++) {
2167 if (this.list[i] == null)
2168 break;
2169 }
2170 this.list[i] = obj;
2171 if (!this.intervalHandle)
2172 this.intervalHandle = setInterval(this.interval, 100);
2173 }
2174 },
2175
2176 // called by setInterval
2177 interval : function () {
2178 var count = 0;
2179 var list = G.VOM.ImageLoader.list, item;
2180 for (var i=0; i<list.length; i++) {
2181 item = list[i];
2182 if (item != null) {
2183 if (item.image.width && item.image.height) {
2184 G.VOM.ImageLoader.list[i] = null;
2185 item.callback (item.image.width, item.image.height, item.ngitem, item.checks);
2186 }
2187 else if (item.checks > G.VOM.ImageLoader.maxChecks) {
2188 G.VOM.ImageLoader.list[i] = null;
2189 item.callback (0, 0, item.ngitem, item.checks);
2190 }
2191 else {
2192 count++;
2193 item.checks++;
2194 }
2195 }
2196 }
2197 if (count == 0) {
2198 G.VOM.ImageLoader.list = [];
2199 clearInterval (G.VOM.ImageLoader.intervalHandle);
2200 delete G.VOM.ImageLoader.intervalHandle;
2201 }
2202 }
2203 }
2204 }
2205 // One VOM item (image)
2206 function VImg(index) {
2207 this.$e = null;
2208 this.ngy2ItemIdx = index;
2209 this.mediaNumber = 0;
2210 this.posX = 0; // to center the element
2211 this.posY = 0;
2212 }
2213
2214
2215 //------------------------
2216 //--- popup
2217 G.popup = {
2218 isDisplayed: false,
2219 $elt: null,
2220 close: function() {
2221 if( this.$elt != null ) {
2222 var tweenable = new NGTweenable();
2223 tweenable.tween({
2224 from: { opacity:1 },
2225 to: { opacity:0 },
2226 attachment: { t: this },
2227 easing: 'easeInOutSine',
2228 duration: 100,
2229 step: function (state, att) {
2230 if( att.t.$elt != null ) {
2231 att.t.$elt.css('opacity',state.opacity);
2232 }
2233 },
2234 finish: function (state, att) {
2235 if( att.t.$elt != null ) {
2236 att.t.$elt.remove();
2237 att.t.$elt=null;
2238 }
2239 att.t.isDisplayed=false;
2240 }
2241 });
2242 }
2243 }
2244 }
2245
2246
2247 // Color schemes - Gallery
2248 // gadrient generator: https://www.grabient.com/
2249 G.galleryTheme_dark = {
2250 navigationBar : { background: 'none', borderTop: '', borderBottom: '', borderRight: '', borderLeft: '' },
2251 navigationBreadcrumb : { background: '#111', color: '#fff', colorHover: '#ccc', borderRadius: '4px' },
2252 navigationFilter : { color: '#ddd', background: '#111', colorSelected: '#fff', backgroundSelected: '#111', borderRadius: '4px' },
2253 thumbnail : { background: '#444', backgroundImage: 'linear-gradient(315deg, #111 0%, #667 90%)', borderColor: '#000', labelOpacity : 1, labelBackground: 'rgba(34, 34, 34, 0)', titleColor: '#fff', titleBgColor: 'transparent', titleShadow: '', descriptionColor: '#ccc', descriptionBgColor: 'transparent', descriptionShadow: '', stackBackground: '#aaa' },
2254 thumbnailIcon : { padding: '5px', color: '#fff' },
2255 pagination : { background: '#181818', backgroundSelected: '#666', color: '#fff', borderRadius: '2px', shapeBorder: '3px solid #666', shapeColor: '#444', shapeSelectedColor: '#aaa'}
2256 };
2257
2258 G.galleryTheme_light = {
2259 navigationBar : { background: 'none', borderTop: '', borderBottom: '', borderRight: '', borderLeft: '' },
2260 navigationBreadcrumb : { background: '#eee', color: '#000', colorHover: '#333', borderRadius: '4px' },
2261 navigationFilter : { background: '#eee', color: '#222', colorSelected: '#000', backgroundSelected: '#eee', borderRadius: '4px' },
2262 thumbnail : { background: '#444', backgroundImage: 'linear-gradient(315deg, #111 0%, #667 90%)', borderColor: '#000', labelOpacity : 1, labelBackground: 'rgba(34, 34, 34, 0)', titleColor: '#fff', titleBgColor: 'transparent', titleShadow: '', descriptionColor: '#ccc', descriptionBgColor: 'transparent', descriptionShadow: '', stackBackground: '#888' },
2263 thumbnailIcon : { padding: '5px', color: '#fff' },
2264 pagination : { background: '#eee', backgroundSelected: '#aaa', color: '#000', borderRadius: '2px', shapeBorder: '3px solid #666', shapeColor: '#444', shapeSelectedColor: '#aaa'}
2265 };
2266
2267 // Color schemes - lightbox
2268 G.viewerTheme_dark = {
2269 background: '#000',
2270 imageBorder: 'none',
2271 imageBoxShadow: 'none',
2272 barBackground: 'rgba(4, 4, 4, 0.7)',
2273 barBorder: '0px solid #111',
2274 barColor: '#eee',
2275 barDescriptionColor: '#aaa'
2276 };
2277 G.viewerTheme_border = {
2278 background: 'rgba(1, 1, 1, 0.75)',
2279 imageBorder: '4px solid #f8f8f8',
2280 imageBoxShadow: '#888 0px 0px 20px',
2281 barBackground: 'rgba(4, 4, 4, 0.7)',
2282 barBorder: '0px solid #111',
2283 barColor: '#eee',
2284 barDescriptionColor: '#aaa'
2285 };
2286 G.viewerTheme_light = {
2287 background: '#f8f8f8',
2288 imageBorder: 'none',
2289 imageBoxShadow: 'none',
2290 barBackground: 'rgba(4, 4, 4, 0.7)',
2291 barBorder: '0px solid #111',
2292 barColor: '#eee',
2293 barDescriptionColor: '#aaa'
2294 };
2295
2296
2297
2298 // shortcut with G context to NGY2TOOLS
2299 // var NanoAlert = NGY2Tools.NanoAlert.bind(G);
2300 // var NanoConsoleLog = NGY2Tools.NanoConsoleLog.bind(G);
2301 var NanoAlert = NGY2Tools.NanoAlert;
2302 var NanoConsoleLog = NGY2Tools.NanoConsoleLog;
2303
2304
2305 /** @function initiateGallery2 */
2306 this.initiateGallery2 = function( element, params ) {
2307
2308 // GLOBAL OPTIONS
2309 G.O = params;
2310 // Base element
2311 G.$E.base = jQuery(element);
2312 G.baseEltID = G.$E.base.attr('id');
2313 if( G.baseEltID == undefined ) {
2314 // set a default ID to the root container
2315 G.baseEltID='my_nanogallery';
2316 G.$E.base.attr('id', G.baseEltID)
2317 }
2318 G.O.$markup = [];
2319 DefineVariables();
2320 SetPolyFills();
2321 BuildSkeleton();
2322 G.GOM.firstDisplayTime=Date.now();
2323
2324 SetGlobalEvents();
2325
2326 // check if only one specific album will be used
2327 var albumToDisplay = G.O.album;
2328 if( albumToDisplay == '' && G.O.photoset != '' ) {
2329 albumToDisplay = G.O.photoset;
2330 G.O.album = G.O.photoset;
2331 }
2332 if( albumToDisplay != '' ) {
2333 G.O.displayBreadcrumb = false; // no breadcrumb since only 1 album
2334 if( albumToDisplay.toUpperCase() != 'NONE' ) {
2335 // open specific album
2336
2337 var p=albumToDisplay.indexOf('&authkey=');
2338 if( p == -1 ) {
2339 p=albumToDisplay.indexOf('?authkey=');
2340 }
2341 if( p > 0 ) {
2342 // privat album with authkey
2343 G.O.locationHash=false; // disable hash location for hidden/privat albums --> impossible to handle
2344 var albumID=albumToDisplay.substring(0,p);
2345 var opt=albumToDisplay.substring(p);
2346 if( opt.indexOf('Gv1sRg') == -1 ) {
2347 opt = '&authkey=Gv1sRg'+opt.substring(9);
2348 }
2349 var newItem = NGY2Item.New( G, '', '', albumID, '-1', 'album' );
2350 newItem.authkey = opt;
2351 DisplayAlbum('-1', albumID);
2352 }
2353 else {
2354 if( G.O.kind == "nano_photos_provider2") {
2355 if( albumToDisplay == decodeURIComponent(albumToDisplay)) {
2356 // album ID must be encoded
2357 albumToDisplay = encodeURIComponent(albumToDisplay);
2358 G.O.album = albumToDisplay;
2359 }
2360 }
2361 NGY2Item.New( G, '', '', albumToDisplay, '-1', 'album' );
2362 DisplayAlbum('-1', albumToDisplay);
2363 }
2364 return;
2365 }
2366 }
2367
2368 // use full content
2369 // add base album
2370 NGY2Item.New( G, G.i18nTranslations.breadcrumbHome, '', '0', '-1', 'album' );
2371
2372
2373 processStartOptions();
2374
2375
2376 }
2377
2378
2379 /** @function processStartOptions */
2380 function processStartOptions() {
2381
2382 // open image or album
2383 // 1. load hidden albums
2384 // 1. check if location hash set (deep linking)
2385 // 2. check openOnStart parameter
2386 // 3. open root album (ID=-1)
2387
2388 // hidden/private albums are loaded on plugin start
2389 if( G.albumListHidden.length > 0 ) {
2390 jQuery.nanogallery2['data_'+G.O.kind](G, 'GetHiddenAlbums', G.albumListHidden, processStartOptionsPart2);
2391 return;
2392 }
2393
2394 if( !ProcessLocationHash() ) {
2395 processStartOptionsPart2();
2396 }
2397 }
2398
2399 /** @function processStartOptionsPart2 */
2400 function processStartOptionsPart2() {
2401
2402 // Check location hash + start parameters -> determine what to do on start
2403 // openOnStart parameter
2404 if( G.O.openOnStart != '' ) {
2405 var IDs=parseIDs(G.O.openOnStart);
2406 if( IDs.imageID != '0' ) {
2407 DisplayPhoto(IDs.imageID, IDs.albumID);
2408 }
2409 else {
2410 DisplayAlbum('-1', IDs.albumID);
2411 }
2412 }
2413 else {
2414 // open root album (ID = -1)
2415 DisplayAlbum('-1', 0);
2416 }
2417 }
2418
2419 // Parse string to extract albumID and imageID (format albumID/imageID)
2420 function parseIDs( IDs ) {
2421 var r={ albumID: '0', imageID: '0' };
2422
2423 var t=IDs.split('/');
2424 if( t.length > 0 ) {
2425 r.albumID=t[0];
2426 if( t.length > 1 ) {
2427 r.imageID=t[1];
2428 }
2429 }
2430 return r;
2431 }
2432
2433
2434 /** @function DisplayAlbum */
2435 function DisplayAlbum( imageID, albumID ) {
2436 // close viewer if already displayed
2437 if( G.VOM.viewerDisplayed ) {
2438 CloseInternalViewer(null);
2439 }
2440
2441 // set current navigation level (l1 or lN)
2442 var albumIdx=NGY2Item.GetIdx(G, albumID);
2443 if( albumIdx == 0 ) {
2444 G.GOM.curNavLevel='l1';
2445 }
2446 else {
2447 G.GOM.curNavLevel='lN';
2448 }
2449 G.layout.SetEngine();
2450 G.galleryResizeEventEnabled=false;
2451
2452 if( albumIdx == -1 ) {
2453 NGY2Item.New( G, '', '', albumID, '0', 'album' ); // create empty album
2454 albumIdx=G.I.length-1;
2455 }
2456
2457 if( !G.I[albumIdx].contentIsLoaded ) {
2458 // get content of the album if not already loaded
2459 AlbumGetContent( albumID, DisplayAlbum, imageID, albumID );
2460 return;
2461 }
2462
2463 ThumbnailSelectionClear();
2464
2465 G.GOM.pagination.currentPage=0;
2466 SetLocationHash( albumID, '' );
2467 GalleryRender( albumIdx );
2468
2469 }
2470
2471
2472 //----- manage the bottom area of the gallery -> "pagination" or "more button"
2473 function GalleryBottomManage() {
2474
2475 switch( G.galleryDisplayMode.Get() ) {
2476 case 'PAGINATION':
2477 if( G.layout.support.rows && G.galleryMaxRows.Get() > 0 ) {
2478 ManagePagination( G.GOM.albumIdx );
2479 }
2480 break;
2481 case 'MOREBUTTON':
2482 G.$E.conTnBottom.off('click');
2483 var nb=G.GOM.items.length-G.GOM.itemsDisplayed;
2484 if( nb == 0 ) {
2485 G.$E.conTnBottom.empty();
2486 }
2487 else {
2488 G.$E.conTnBottom.html('<div class="nGY2GalleryMoreButton"><div class="nGY2GalleryMoreButtonAnnotation">+'+nb+' ' + G.O.icons.galleryMoreButton +'</div></div>');
2489 G.$E.conTnBottom.on('click', function(e) {
2490 G.GOM.displayedMoreSteps++;
2491 GalleryResize();
2492 });
2493 }
2494 break;
2495 case 'FULLCONTENT':
2496 default:
2497 break;
2498 }
2499 }
2500
2501
2502 // add one album/folder to the breadcrumb
2503 function breadcrumbAdd( albumIdx ) {
2504
2505 var ic='';
2506 if( !G.O.breadcrumbHideIcons ) {
2507 ic=G.O.icons.breadcrumbAlbum;
2508 if( albumIdx == 0 ) {
2509 ic=G.O.icons.breadcrumbHome;
2510 }
2511 }
2512 var $newDiv =jQuery('<div class="oneItem">'+ic + G.I[albumIdx].title+'</div>').appendTo(G.GOM.navigationBar.$newContent.find('.nGY2Breadcrumb'));
2513 if( G.O.breadcrumbOnlyCurrentLevel ) {
2514 // link to parent folder (only 1 level is displayed in the breadcrumb)
2515 if( albumIdx == 0 ) {
2516 // no parent level -> stay on current one
2517 jQuery($newDiv).data('albumID','0');
2518 }
2519 else {
2520 jQuery($newDiv).data('albumID',G.I[albumIdx].albumID);
2521 }
2522 }
2523 else {
2524 // link to current folder
2525 jQuery($newDiv).data('albumID',G.I[albumIdx].GetID());
2526 }
2527 $newDiv.click(function() {
2528 var cAlbumID=jQuery(this).data('albumID');
2529 DisplayAlbum('-1', cAlbumID);
2530 return;
2531 });
2532 }
2533
2534 // add one separator to breadcrumb
2535 function breadcrumbAddSeparator( lastAlbumID ) {
2536 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'));
2537 jQuery($newSep).data('albumIdx',lastAlbumID);
2538 $newSep.click(function() {
2539 var sepAlbumIdx=jQuery(this).data('albumIdx');
2540 DisplayAlbum('-1', G.I[sepAlbumIdx].GetID());
2541 return;
2542 });
2543 }
2544
2545
2546
2547 // Manage the gallery toolbar (breadcrumb + tag filter)
2548 function GalleryNavigationBar( albumIdx ) {
2549
2550 // Title + background image
2551 // var bgImage='';
2552 // var l=G.I.length;
2553 // var albumID = G.I[albumIdx].GetID();
2554 // for( var idx=0; idx<l ; idx++) {
2555 // var item=G.I[idx];
2556 // if( item.kind == 'image' && item.isToDisplay(albumID) ) {
2557 // 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>';
2558 // break;
2559 // }
2560 // }
2561
2562 //console.log(bgImage);
2563
2564 // new navigation bar items are not build in the DOM, but in memory
2565 G.GOM.navigationBar.$newContent=jQuery('<div class="nGY2Navigationbar"></div>');
2566 //G.GOM.navigationBar.$newContent = jQuery(bgImage );
2567 //console.log(G.GOM.navigationBar.$newContent);
2568
2569 //-- manage breadcrumb
2570 if( G.O.displayBreadcrumb == true && !G.O.thumbnailAlbumDisplayImage) {
2571 // retrieve new folder level
2572 var newLevel = 0,
2573 lstItems=[];
2574 if( albumIdx != 0 ) {
2575 var l=G.I.length,
2576 parentID=0;
2577
2578 lstItems.push(albumIdx);
2579 var curIdx=albumIdx;
2580 newLevel++;
2581
2582 while( G.I[curIdx].albumID != 0 && G.I[curIdx].albumID != -1) {
2583 for(var i=1; i < l; i++ ) {
2584 if( G.I[i].GetID() == G.I[curIdx].albumID ) {
2585 curIdx=i;
2586 lstItems.push(curIdx);
2587 newLevel++;
2588 break;
2589 }
2590 }
2591 }
2592 }
2593
2594 // build breadcrumb
2595 if( !(G.O.breadcrumbAutoHideTopLevel && newLevel == 0) ) {
2596 BreadcrumbBuild( lstItems );
2597 }
2598 }
2599
2600
2601 //-- manage and build tag filters
2602 if( G.galleryFilterTags.Get() != false ) {
2603 var nTags=G.I[albumIdx].albumTagList.length;
2604 if( nTags > 0 ) {
2605 for(var i=0; i < nTags; i++ ) {
2606 var s=G.I[albumIdx].albumTagList[i];
2607 var ic=G.O.icons.navigationFilterUnselected;
2608 var tagClass='Unselected';
2609 if( jQuery.inArray(s, G.I[albumIdx].albumTagListSel) >= 0 ) {
2610 tagClass='Selected';
2611 ic=G.O.icons.navigationFilterSelected;
2612 }
2613 var $newTag=jQuery('<div class="nGY2NavigationbarItem nGY2NavFilter'+tagClass+'">'+ic+' '+s+'</div>').appendTo(G.GOM.navigationBar.$newContent);
2614 $newTag.click(function() {
2615 var $this=jQuery(this);
2616 var tag=$this.text().replace(/^\s*|\s*$/, ''); //trim trailing/leading whitespace
2617 // if( $this.hasClass('oneTagUnselected') ){
2618 if( $this.hasClass('nGY2NavFilterUnselected') ){
2619 G.I[albumIdx].albumTagListSel.push(tag);
2620 }
2621 else {
2622 var tidx=jQuery.inArray(tag,G.I[albumIdx].albumTagListSel);
2623 if( tidx != -1 ) {
2624 G.I[albumIdx].albumTagListSel.splice(tidx,1);
2625 }
2626 }
2627 $this.toggleClass('nGY2NavFilters-oneTagUnselected nGY2NavFilters-oneTagSelected');
2628 DisplayAlbum('-1', G.I[albumIdx].GetID());
2629 });
2630 }
2631 var $newClearFilter=jQuery('<div class="nGY2NavigationbarItem nGY2NavFilterSelectAll">'+G.O.icons.navigationFilterSelectedAll+'</div>').appendTo(G.GOM.navigationBar.$newContent);
2632 $newClearFilter.click(function() {
2633 var nTags=G.I[albumIdx].albumTagList.length;
2634 G.I[albumIdx].albumTagListSel=[];
2635 for(var i=0; i <nTags; i++ ) {
2636 var s=G.I[albumIdx].albumTagList[i];
2637 G.I[albumIdx].albumTagListSel.push(s);
2638 }
2639 DisplayAlbum('-1', G.I[albumIdx].GetID());
2640 });
2641 }
2642 }
2643
2644 }
2645
2646 function BreadcrumbBuild(lstItems) {
2647
2648 // console.log(G.GOM.navigationBar.$newContent);
2649 jQuery('<div class="nGY2NavigationbarItem nGY2Breadcrumb"></div>').appendTo(G.GOM.navigationBar.$newContent);
2650 // console.log(G.GOM.navigationBar.$newContent);
2651
2652 if( G.O.breadcrumbOnlyCurrentLevel ) {
2653 // display only 1 separator and the current folder level
2654 if( lstItems.length == 0 ) {
2655 breadcrumbAdd(0);
2656 }
2657 else {
2658 var last=lstItems.length-1;
2659 if( lstItems.length == 1 ) {
2660 breadcrumbAddSeparator(0); // root level
2661 }
2662 else {
2663 breadcrumbAddSeparator(lstItems[0]);
2664 }
2665 breadcrumbAdd(lstItems[0]);
2666 }
2667 }
2668 else {
2669 // display the full breadcrum (full folder levels including root level)
2670 breadcrumbAdd(0);
2671 if( lstItems.length > 0 ) {
2672 breadcrumbAddSeparator(0);
2673 for(var i=lstItems.length-1; i>=0 ; i-- ) {
2674 breadcrumbAdd(lstItems[i]);
2675 if( i > 0 ) {
2676 breadcrumbAddSeparator(lstItems[i-1]);
2677 }
2678 }
2679 }
2680 }
2681
2682 }
2683
2684
2685 // Display gallery pagination
2686 function ManagePagination( albumIdx ) {
2687
2688 G.$E.conTnBottom.css('opacity', 0);
2689 G.$E.conTnBottom.children().remove();
2690
2691 if( G.GOM.items.length == 0 ) { return; } // no thumbnail to display
2692
2693 // calculate the number of pages
2694 var nbPages=Math.ceil((G.GOM.items[G.GOM.items.length - 1].row + 1)/G.galleryMaxRows.Get());
2695
2696 // only one page -> do not display pagination
2697 if( nbPages == 1 ) { return; }
2698
2699 // check if current page still exists (for example after a resize)
2700 if( G.GOM.pagination.currentPage > (nbPages-1) ) {
2701 G.GOM.pagination.currentPage = nbPages-1;
2702 }
2703
2704 GalleryRenderGetInterval();
2705 // nothing to display --> exit
2706 if( G.GOM.displayInterval.len == 0 ) { return; }
2707
2708 // display "previous"
2709 if( G.O.galleryPaginationMode == 'NUMBERS' && G.GOM.pagination.currentPage > 0 ) {
2710 var $eltPrev = jQuery('<div class="nGY2PaginationPrev">'+G.O.icons.paginationPrevious+'</div>').appendTo(G.$E.conTnBottom);
2711 $eltPrev.click(function(e) {
2712 paginationPreviousPage();
2713 });
2714 }
2715
2716 var firstPage = 0;
2717 var lastPage = nbPages;
2718 if( G.O.galleryPaginationMode != 'NUMBERS' ) {
2719 // no 'previous'/'next' and no max number of pagination items
2720 firstPage = 0;
2721 }
2722 else {
2723 // display pagination numbers and previous/next
2724 var vp = G.O.paginationVisiblePages;
2725 var numberOfPagesToDisplay = G.O.paginationVisiblePages;
2726 if( numberOfPagesToDisplay >= nbPages ) {
2727 firstPage = 0;
2728 }
2729 else {
2730 // we have more pages than we want to display
2731 var nbBeforeAfter = 0;
2732 if( isOdd(numberOfPagesToDisplay) ) {
2733 nbBeforeAfter = (numberOfPagesToDisplay + 1) / 2;
2734 }
2735 else {
2736 nbBeforeAfter = numberOfPagesToDisplay / 2;
2737 }
2738
2739 if( G.GOM.pagination.currentPage < nbBeforeAfter ) {
2740 firstPage = 0;
2741 lastPage = numberOfPagesToDisplay - 1;
2742 if( lastPage > nbPages ) {
2743 lastPage = nbPages - 1;
2744 }
2745 }
2746 else {
2747 firstPage = G.GOM.pagination.currentPage - nbBeforeAfter;
2748 lastPage = firstPage + numberOfPagesToDisplay;
2749 if( lastPage > nbPages ) {
2750 lastPage = nbPages - 1;
2751 }
2752 }
2753
2754 if( (lastPage - firstPage) < numberOfPagesToDisplay ) {
2755 firstPage = lastPage - numberOfPagesToDisplay;
2756 if( firstPage < 0 ) {
2757 firstPage = 0;
2758 }
2759 }
2760
2761 }
2762 }
2763
2764 // render pagination items
2765 for(var i = firstPage; i < lastPage; i++ ) {
2766 var c = '';
2767 var p = '';
2768
2769 switch( G.O.galleryPaginationMode ) {
2770 case 'NUMBERS':
2771 c = 'nGY2paginationItem';
2772 p = i + 1;
2773 break;
2774 case 'DOTS':
2775 c = 'nGY2paginationDot';
2776 break;
2777 case 'RECTANGLES':
2778 c = 'nGY2paginationRectangle';
2779 break;
2780 }
2781 if( i == G.GOM.pagination.currentPage ) {
2782 c += 'CurrentPage';
2783 }
2784
2785 var elt$ = jQuery('<div class="' + c + '">' + p + '</div>').appendTo(G.$E.conTnBottom);
2786 elt$.data('pageNumber', i );
2787 elt$.click( function(e) {
2788 G.GOM.pagination.currentPage = jQuery(this).data('pageNumber');
2789 TriggerCustomEvent('pageChanged');
2790 GalleryDisplayPart1( true );
2791 GalleryDisplayPart2( true );
2792 });
2793
2794 }
2795
2796 // display "next"
2797 if( G.O.galleryPaginationMode == 'NUMBERS' && (G.GOM.pagination.currentPage + 1) < nbPages ) {
2798 var $eltNext = jQuery('<div class="nGY2PaginationNext">' + G.O.icons.paginationNext + '</div>').appendTo(G.$E.conTnBottom);
2799 $eltNext.click( function(e) {
2800 paginationNextPage();
2801 });
2802 }
2803
2804 G.$E.conTnBottom.css('opacity', 1);
2805
2806 }
2807 function isOdd(num) { return (num % 2) == 1;}
2808
2809 // pagination - next page
2810 function paginationNextPage() {
2811 var aIdx = G.GOM.albumIdx,
2812 n1 = 0;
2813 ThumbnailHoverOutAll();
2814
2815 // pagination - max lines per page mode
2816 if( G.galleryMaxRows.Get() > 0 ) {
2817 // number of pages
2818 n1 = (G.GOM.items[G.GOM.items.length - 1].row + 1) / G.galleryMaxRows.Get();
2819 }
2820 var n2 = Math.ceil(n1);
2821 var pn = G.GOM.pagination.currentPage;
2822 if( pn < (n2-1) ) {
2823 pn++;
2824 }
2825 else {
2826 pn = 0;
2827 }
2828
2829 G.GOM.pagination.currentPage = pn;
2830 TriggerCustomEvent('pageChanged');
2831
2832 GalleryDisplayPart1( true );
2833 GalleryDisplayPart2( true );
2834 }
2835
2836 // pagination - previous page
2837 function paginationPreviousPage() {
2838 // var aIdx=G.$E.conTnBottom.data('galleryIdx'),
2839 var aIdx = G.GOM.albumIdx,
2840 n1 = 0;
2841
2842 ThumbnailHoverOutAll();
2843
2844 // pagination - max lines per page mode
2845 if( G.galleryMaxRows.Get() > 0 ) {
2846 // number of pages
2847 n1 = (G.GOM.items[G.GOM.items.length - 1].row + 1) / G.galleryMaxRows.Get();
2848 }
2849 var n2 = Math.ceil(n1);
2850
2851 // var pn=G.$E.conTnBottom.data('currentPageNumber');
2852 var pn = G.GOM.pagination.currentPage;
2853 if( pn > 0 ) {
2854 pn--;
2855 }
2856 else {
2857 pn = n2 - 1;
2858 }
2859
2860 G.GOM.pagination.currentPage = pn;
2861 TriggerCustomEvent('pageChanged');
2862 GalleryDisplayPart1( true );
2863 GalleryDisplayPart2( true );
2864 }
2865
2866 // retrieve the from/to intervall for gallery thumbnail render
2867 function GalleryRenderGetInterval() {
2868 G.GOM.displayInterval.from = 0;
2869 G.GOM.displayInterval.len = G.I.length;
2870
2871 switch( G.galleryDisplayMode.Get() ) {
2872 case 'PAGINATION':
2873 if( G.layout.support.rows ) {
2874 var nbTn = G.GOM.items.length;
2875 var firstRow = G.GOM.pagination.currentPage * G.galleryMaxRows.Get();
2876 var lastRow = firstRow + G.galleryMaxRows.Get();
2877 var firstTn = -1;
2878 G.GOM.displayInterval.len = 0;
2879 for( var i = 0; i < nbTn ; i++ ) {
2880 var curTn = G.GOM.items[i];
2881 if( curTn.row >= firstRow && curTn.row < lastRow ) {
2882 if( firstTn == -1 ) {
2883 G.GOM.displayInterval.from = i;
2884 firstTn = i;
2885 }
2886 G.GOM.displayInterval.len++;
2887 }
2888 }
2889 }
2890 break;
2891 case 'MOREBUTTON':
2892 if( G.layout.support.rows ) {
2893 var nbTn = G.GOM.items.length;
2894 var lastRow = G.O.galleryDisplayMoreStep * (G.GOM.displayedMoreSteps+1);
2895 G.GOM.displayInterval.len = 0;
2896 for( var i = 0; i < nbTn ; i++ ) {
2897 var curTn = G.GOM.items[i];
2898 if( curTn.row < lastRow ) {
2899 G.GOM.displayInterval.len++;
2900 }
2901 }
2902 }
2903 break;
2904 case 'ROWS':
2905 if( G.layout.support.rows ) {
2906 var nbTn = G.GOM.items.length;
2907 var lastRow = G.galleryMaxRows.Get();
2908 if( G.galleryLastRowFull.Get() && G.GOM.lastFullRow != -1 ) {
2909 if( lastRow > (G.GOM.lastFullRow + 1) ) {
2910 lastRow = G.GOM.lastFullRow + 1;
2911 }
2912 }
2913 G.GOM.displayInterval.len = 0;
2914 for( var i = 0; i < nbTn ; i++ ) {
2915 var curTn = G.GOM.items[i];
2916 if( curTn.row < lastRow ) {
2917 G.GOM.displayInterval.len++;
2918 }
2919 }
2920 }
2921 break;
2922 default:
2923 case 'FULLCONTENT':
2924 if( G.layout.support.rows && G.galleryLastRowFull.Get() && G.GOM.lastFullRow != -1 ) {
2925 var nbTn = G.GOM.items.length;
2926 var lastRow = G.GOM.lastFullRow + 1;
2927 G.GOM.displayInterval.len = 0;
2928 for( var i = 0; i < nbTn ; i++ ) {
2929 var curTn = G.GOM.items[i];
2930 if( curTn.row < lastRow ) {
2931 G.GOM.displayInterval.len++;
2932 }
2933 }
2934 }
2935 break;
2936 }
2937 }
2938
2939
2940 // RENDER THE GALLERY
2941 function GalleryRender( albumIdx ) {
2942 TriggerCustomEvent('galleryRenderStart');
2943
2944 clearTimeout(G.GOM.slider.timerID);
2945 G.GOM.slider.hostIdx = -1; // disabled slider on thumbnail
2946
2947 var fu=G.O.fnGalleryRenderStart;
2948 if( fu !== null ) {
2949 fu == 'function' ? fu(albumIdx) : window[fu](albumIdx);
2950 }
2951
2952 G.layout.SetEngine();
2953 G.galleryResizeEventEnabled=false;
2954 G.GOM.albumIdx = -1;
2955 G.GOM.lastDisplayedIdx = -1;
2956
2957 // pagination
2958 if( G.$E.conTnBottom !== undefined ) {
2959 // G.$E.conTnBottom.children().remove();
2960 G.$E.conTnBottom.empty();
2961 }
2962
2963 // navigation toolbar (breadcrumb + tag filters)
2964 GalleryNavigationBar(albumIdx);
2965
2966 if( G.GOM.firstDisplay ) {
2967 G.GOM.firstDisplay = false;
2968 var d = Date.now()-G.GOM.firstDisplayTime;
2969 if( d < G.O.galleryRenderDelay ) {
2970 setTimeout( function() { GalleryRenderPart1( albumIdx )}, G.O.galleryRenderDelay-d );
2971 }
2972 else {
2973 GalleryRenderPart1( albumIdx );
2974 }
2975 G.O.galleryRenderDelay = 0;
2976
2977 }
2978 else {
2979 var hideNavigationBar = false;
2980 if( G.GOM.navigationBar.$newContent.children().length == 0 ) {
2981 hideNavigationBar = true;
2982 }
2983
2984 // hide everything
2985 var tweenable = new NGTweenable();
2986 tweenable.tween({
2987 from: { 'opacity': 1 },
2988 to: { 'opacity': 0 },
2989 duration: 300,
2990 easing: 'easeInQuart',
2991 attachment: { h: hideNavigationBar },
2992 step: function (state, att) {
2993 G.$E.conTnParent.css({'opacity': state.opacity });
2994 if( att.h ) {
2995 G.$E.conNavigationBar.css({ 'opacity': state.opacity });
2996 }
2997 },
2998 finish: function (state, att) {
2999 G.$E.conTnParent.css({'opacity': 0});
3000 if( att.h ) {
3001 G.$E.conNavigationBar.css({ 'opacity': 0, 'display': 'none' });
3002 }
3003 // scroll to top of the gallery if needed
3004 var galleryOTop = G.$E.base.offset().top;
3005 if( galleryOTop < G.GOM.cache.viewport.t ) {
3006 // jQuery('html, body').animate({scrollTop: galleryOTop}, 200);
3007 jQuery('html, body').animate({scrollTop: galleryOTop}, 500, "linear", function() {
3008 GalleryRenderPart1( albumIdx );
3009 });
3010 }
3011 else {
3012 GalleryRenderPart1( albumIdx );
3013 }
3014 }
3015 });
3016 }
3017 }
3018
3019
3020 function GalleryRenderPart1( albumIdx ) {
3021 // display new navigation bar
3022 var oldN = G.$E.conNavigationBar.children().length;
3023 G.$E.conNavigationBar.empty();
3024 G.GOM.navigationBar.$newContent.children().clone(true,true).appendTo(G.$E.conNavigationBar);
3025 // G.GOM.navigationBar.$newContent.appendTo(G.$E.conNavigationBar);
3026 if( G.$E.conNavigationBar.children().length > 0 && oldN == 0 ) {
3027 G.$E.conNavigationBar.css({ 'opacity': 0, 'display': 'block' });
3028 var tweenable = new NGTweenable();
3029 tweenable.tween({
3030 from: { opacity: 0 },
3031 to: { opacity: 1 },
3032 duration: 200,
3033 easing: 'easeInQuart',
3034 step: function (state) {
3035 G.$E.conNavigationBar.css( state );
3036 },
3037 finish: function (state) {
3038 G.$E.conNavigationBar.css({ 'opacity': 1 });
3039 // display gallery
3040 // GalleryRenderPart2( albumIdx );
3041 setTimeout(function(){ GalleryRenderPart2(albumIdx) }, 60);
3042 }
3043 });
3044 }
3045 else {
3046 // display gallery
3047 // GalleryRenderPart2( albumIdx );
3048 setTimeout(function(){ GalleryRenderPart2(albumIdx) }, 60);
3049 }
3050
3051 }
3052
3053 // Gallery render part 2 -> remove all thumbnails
3054 function GalleryRenderPart2(albumIdx) {
3055 G.GOM.lastZIndex = parseInt(G.$E.base.css('z-index'));
3056 if( isNaN(G.GOM.lastZIndex) ) {
3057 G.GOM.lastZIndex=0;
3058 }
3059 G.$E.conTnParent.css({ 'opacity': 0 });
3060 G.$E.conTn.off().empty();
3061 var l = G.I.length;
3062 for( var i = 0; i < l ; i++ ) {
3063 // reset each item
3064 var item = G.I[i];
3065 item.hovered = false;
3066 item.$elt = null;
3067 item.$Elts = [];
3068 item.eltTransform = [];
3069 item.eltFilter = [];
3070 item.width = 0;
3071 item.height = 0;
3072 item.left = 0;
3073 item.top = 0;
3074 item.resizedContentWidth = 0;
3075 item.resizedContentHeight = 0;
3076 item.thumbnailImgRevealed = false;
3077 }
3078
3079 if( G.CSStransformName == null ) {
3080 G.$E.conTn.css('left', '0px');
3081 }
3082 else {
3083 // G.$E.conTn.css( G.CSStransformName, 'translateX(0px)');
3084 G.$E.conTn.css( G.CSStransformName, 'none');
3085 }
3086
3087 setTimeout(function(){ GalleryRenderPart3(albumIdx) }, 60);
3088 // GalleryRenderPart3(albumIdx);
3089
3090 }
3091
3092 // Gallery render part 2 -> start building the new gallery
3093 function GalleryRenderPart3(albumIdx) {
3094 var d = new Date();
3095
3096 G.$E.conTnParent.css( 'opacity', 1);
3097
3098 G.GOM.items = [];
3099 G.GOM.displayedMoreSteps = 0;
3100 // retrieve label height
3101 if( G.O.thumbnailLabel.get('position') == 'onBottom' ) {
3102 // retrieve height each time because size can change depending on thumbnail's settings
3103 G.tn.labelHeight[G.GOM.curNavLevel]=ThumbnailGetLabelHeight();
3104 }
3105 else {
3106 G.tn.labelHeight[G.GOM.curNavLevel]=0;
3107 }
3108 G.GOM.albumIdx=albumIdx;
3109
3110 TriggerCustomEvent('galleryRenderEnd');
3111 var fu=G.O.fnGalleryRenderEnd;
3112 if( fu !== null ) {
3113 fu == 'function' ? fu(albumIdx) : window[fu](albumIdx);
3114 }
3115
3116 // Step 1: populate GOM
3117 if( GalleryPopulateGOM() ) {
3118
3119 // step 2: calculate layout
3120 GallerySetLayout();
3121
3122 // step 3: display gallery
3123 GalleryAppear();
3124 // GalleryDisplay( false );
3125 GalleryDisplayPart1( false );
3126 setTimeout(function(){ GalleryDisplayPart2( false ) }, 120);
3127 }
3128 else {
3129 G.galleryResizeEventEnabled=true;
3130 }
3131
3132 if( G.O.debugMode ) { console.log('GalleryRenderPart3: '+ (new Date()-d)); }
3133
3134 }
3135
3136
3137 // Resize the gallery
3138 function GalleryResize() {
3139 var d = new Date();
3140 G.galleryResizeEventEnabled = false;
3141 // G.GOM.cache.areaWidth=G.$E.conTnParent.width();
3142 if( GallerySetLayout() == false ) {
3143 G.galleryResizeEventEnabled = true;
3144 if( G.O.debugMode ) { console.log('GalleryResize1: '+ (new Date()-d)); }
3145 return;
3146 }
3147 if( G.O.debugMode ) { console.log('GalleryResizeSetLayout: '+ (new Date()-d)); }
3148
3149 GalleryDisplayPart1( false );
3150 GalleryDisplayPart2( false );
3151
3152 if( G.O.debugMode ) { console.log('GalleryResizeFull: '+ (new Date()-d)); }
3153 }
3154
3155
3156
3157 // copy items (album content) to GOM
3158 function GalleryPopulateGOM() {
3159
3160 var preloadImages='';
3161 var imageSizeRequested=false;
3162 var albumID=G.I[G.GOM.albumIdx].GetID();
3163 var l=G.I.length;
3164 var cnt=0;
3165
3166 for( var idx=0; idx < l; idx++ ) {
3167 var item=G.I[idx];
3168 // check album
3169 if( item.isToDisplay(albumID) ) {
3170 var w=item.thumbImg().width;
3171 var h=item.thumbImg().height;
3172 // if unknown image size and layout is not grid --> we need to retrieve the size of the images
3173 if( G.layout.prerequisite.imageSize && ( w == 0 || h == 0) ) {
3174 // if( true ) {
3175 imageSizeRequested=true;
3176 preloadImages+='<img src="'+item.thumbImg().src+'" data-idx="'+cnt+'" data-albumidx="'+G.GOM.albumIdx+'">';
3177 }
3178
3179 // set default size if required
3180 if( h == 0 ) {
3181 h=G.tn.defaultSize.getHeight();
3182 }
3183 if( w == 0 ) {
3184 w=G.tn.defaultSize.getWidth();
3185 }
3186 var tn=new GTn(idx, w, h);
3187 G.GOM.items.push(tn);
3188 cnt++;
3189 }
3190 }
3191
3192 TriggerCustomEvent('galleryObjectModelBuilt');
3193 var fu=G.O.fnGalleryObjectModelBuilt;
3194 if( fu !== null ) {
3195 fu == 'function' ? fu() : window[fu]();
3196 }
3197
3198 if( imageSizeRequested ) {
3199 // preload images to retrieve their size and then resize the gallery (=GallerySetLayout()+ GalleryDisplay())
3200 var $newImg=jQuery(preloadImages);
3201 var gi_imgLoad = ngimagesLoaded( $newImg );
3202 $newImg=null;
3203 gi_imgLoad.on( 'progress', function( instance, image ) {
3204
3205 if( image.isLoaded ) {
3206 var idx=image.img.getAttribute('data-idx');
3207 var albumIdx=image.img.getAttribute('data-albumidx');
3208 if( albumIdx == G.GOM.albumIdx ) {
3209 // ignore event if not on current album
3210 var curTn=G.GOM.items[idx];
3211 curTn.imageWidth=image.img.naturalWidth;
3212 curTn.imageHeight=image.img.naturalHeight;
3213 var item=G.I[curTn.thumbnailIdx];
3214 item.thumbs.width[G.GOM.curNavLevel][G.GOM.curWidth]=curTn.imageWidth;
3215 item.thumbs.height[G.GOM.curNavLevel][G.GOM.curWidth]=curTn.imageHeight;
3216
3217 // resize the gallery
3218 G.GalleryResizeThrottled();
3219
3220 // set the retrieved size to all levels with same configuration
3221 var object=item.thumbs.width.l1;
3222 for (var property in object) {
3223 if (object.hasOwnProperty(property)) {
3224 if( property != G.GOM.curWidth ) {
3225 if( G.tn.settings.width.l1[property] == G.tn.settings.getW() && G.tn.settings.height.l1[property] == G.tn.settings.getH() ) {
3226 item.thumbs.width.l1[property]=curTn.imageWidth;
3227 item.thumbs.height.l1[property]=curTn.imageHeight;
3228 }
3229 }
3230 }
3231 }
3232 object=item.thumbs.width.lN;
3233 for (var property in object) {
3234 if (object.hasOwnProperty(property)) {
3235 if( property != G.GOM.curWidth ) {
3236 if( G.tn.settings.width.lN[property] == G.tn.settings.getW() && G.tn.settings.height.lN[property] == G.tn.settings.getH() ) {
3237 item.thumbs.width.lN[property]=curTn.imageWidth;
3238 item.thumbs.height.lN[property]=curTn.imageHeight;
3239 }
3240 }
3241 }
3242 }
3243 }
3244 }
3245 });
3246 G.galleryResizeEventEnabled=true;
3247 return false;
3248 }
3249 else {
3250 return true;
3251 }
3252
3253 }
3254
3255 //----- Calculate the layout of the thumbnails
3256 function GallerySetLayout() {
3257 var r = true;
3258 // width of the available area
3259 G.GOM.cache.areaWidth = G.$E.conTnParent.width();
3260 G.GOM.displayArea = { width:0, height:0 };
3261
3262 switch( G.layout.engine ) {
3263 case 'JUSTIFIED':
3264 r = GallerySetLayoutWidthtAuto();
3265 break;
3266 case 'CASCADING':
3267 r = GallerySetLayoutHeightAuto();
3268 break;
3269 case 'MOSAIC':
3270 r = GallerySetLayoutMosaic();
3271 break;
3272 case 'GRID':
3273 default:
3274 r = GallerySetLayoutGrid();
3275 break;
3276 }
3277
3278 TriggerCustomEvent('galleryLayoutApplied');
3279 var fu = G.O.fnGalleryLayoutApplied;
3280 if( fu !== null ) {
3281 fu == 'function' ? fu() : window[fu]();
3282 }
3283 return r;
3284
3285 }
3286
3287
3288 //----- CASCADING LAYOUT
3289 function GallerySetLayoutHeightAuto() {
3290 var curCol = 0,
3291 areaWidth = G.GOM.cache.areaWidth,
3292 curRow = 0,
3293 colHeight = [],
3294 maxCol = NbThumbnailsPerRow(areaWidth),
3295 gutterWidth = 0,
3296 gutterHeight = G.tn.opt.Get('gutterHeight');
3297 var w = 0;
3298 var scaleFactor = 1;
3299 var tnWidth = G.tn.defaultSize.getOuterWidth();
3300 var nbTn = G.GOM.items.length;
3301 var curPosY = 0;
3302
3303 if( G.O.thumbnailAlignment == 'justified' ) {
3304 maxCol = Math.min(maxCol, nbTn);
3305 gutterWidth = ( maxCol == 1 ? 0 : (areaWidth - (maxCol * tnWidth) ) / (maxCol - 1) );
3306 }
3307 else {
3308 gutterWidth=G.tn.opt.Get('gutterWidth');
3309 }
3310
3311
3312 var borderWidth = G.tn.borderWidth * 2;
3313 var borderHeight = G.tn.borderHeight * 2;
3314
3315 G.GOM.lastFullRow=-1; // feature disabled
3316
3317 // Retrieve the real used width of the area (the evaluation is based on the content of the first line)
3318 if( G.O.thumbnailAlignment == 'fillWidth' ) {
3319 // fillWidth --> evaluate scale factor and number of columns
3320 var totalGutterWidth = (maxCol - 1) * gutterWidth;
3321 scaleFactor = (areaWidth - totalGutterWidth) / (maxCol * tnWidth);
3322 if( scaleFactor > 1 ) {
3323 maxCol++; // add one column and re-evaluate the scale factor
3324 }
3325 totalGutterWidth = (maxCol - 1) * gutterWidth;
3326 scaleFactor = Math.min( (areaWidth - totalGutterWidth) / (maxCol*tnWidth), 1); // no upscale
3327 }
3328
3329
3330 tnWidth = tnWidth * scaleFactor;
3331 var contentWidth = tnWidth - borderWidth;
3332
3333 // loop to position the thumbnails, and set their size
3334 var baseHeight = G.tn.opt.Get('baseGridHeight') * scaleFactor;
3335 for( var i = 0; i < nbTn ; i++ ) {
3336 var curTn = G.GOM.items[i];
3337 if( curTn.imageHeight > 0 && curTn.imageWidth > 0 ) {
3338 var curPosX = 0,
3339 curPosY = 0;
3340 var imageRatio = curTn.imageHeight / curTn.imageWidth;
3341 // curTn.resizedContentWidth = tnWidth - borderWidth;
3342 curTn.resizedContentWidth = contentWidth;
3343 curTn.resizedContentHeight = curTn.resizedContentWidth * imageRatio;
3344 if( baseHeight > 0 ) {
3345 // grid based vertical position
3346 var t= Math.max( Math.trunc(curTn.resizedContentHeight/baseHeight), 1) ;
3347 curTn.resizedContentHeight = baseHeight * t + ((t-1)*(borderHeight+gutterHeight));
3348 }
3349
3350 curTn.height = curTn.resizedContentHeight + borderHeight + G.tn.labelHeight.get();
3351 curTn.width = tnWidth;
3352 curTn.row = 0;
3353
3354 if( curRow == 0 ) {
3355 // first row
3356 curPosX = curCol * (tnWidth + gutterWidth);
3357 colHeight[curCol] = curTn.height + gutterHeight;
3358
3359 curCol++;
3360 if( curCol >= maxCol ) {
3361 curCol = 0;
3362 curRow++;
3363 }
3364 }
3365 else {
3366 var c=0,
3367 minColHeight=colHeight[0];
3368 for( var j = 1; j < maxCol; j++) {
3369 if( (colHeight[j] + 5) < minColHeight ) { // +5 --> threshold
3370 minColHeight = colHeight[j];
3371 c = j;
3372 //break;
3373 }
3374 }
3375 curPosY = colHeight[c];
3376 curPosX = c * (tnWidth + gutterWidth);
3377 colHeight[c] = curPosY + curTn.height + gutterHeight;
3378 }
3379
3380 var x = curPosX;
3381 if( G.O.RTL) {
3382 x= w - curPosX - tnWidth;
3383 }
3384
3385 curTn.left = x;
3386 curTn.top = curPosY;
3387 }
3388 }
3389
3390 G.GOM.displayArea.width= maxCol * (tnWidth + gutterWidth) - gutterWidth;
3391 return true;
3392 }
3393
3394
3395 //----- JUSTIFIED LAYOUT
3396 function GallerySetLayoutWidthtAuto() {
3397 var curWidth = 0,
3398 areaWidth = G.GOM.cache.areaWidth,
3399 lastPosX = 0,
3400 curPosY = 0,
3401 rowLastItem = [],
3402 rowNum = 0,
3403 rowHeight = [],
3404 bNewRow = false,
3405 cnt = 0,
3406 gutterWidth = G.tn.opt.Get('gutterWidth'),
3407 gutterHeight = G.tn.opt.Get('gutterHeight');
3408 // by grief-of-these-days
3409 var maxRowHeightVertical = 0; // max height of a row with vertical thumbs
3410 var maxRowHeightHorizontal = 0; // max height of a row with horizontal thumbs
3411 var rowHasVertical = false; // current row has vertical thumbs
3412 var rowHasHorizontal = false; // current row has horizontal thumbs
3413
3414 var tnHeight = G.tn.defaultSize.getOuterHeight();
3415 var borderWidth = G.tn.borderWidth * 2;
3416 var borderHeight = G.tn.borderHeight * 2;
3417 var nbTnInCurrRow = 1;
3418 var nbTn = G.GOM.items.length;
3419
3420 // first loop --> retrieve each row image height
3421 for( var i = 0; i < nbTn ; i++ ) {
3422 var curTn = G.GOM.items[i];
3423 if( curTn.imageWidth > 0 ) {
3424 var imageRatio = curTn.imageWidth / curTn.imageHeight;
3425 var imageWidth = Math.floor( tnHeight * imageRatio );
3426
3427 if( bNewRow ) {
3428 bNewRow = false;
3429 rowNum++;
3430 curWidth = 0;
3431 rowHasVertical = false;
3432 rowHasHorizontal = false;
3433 nbTnInCurrRow = 1;
3434 }
3435 // by grief-of-these-days
3436 if( curTn.imageHeight > curTn.imageWidth ) {
3437 rowHasVertical = true;
3438 }
3439 else {
3440 rowHasHorizontal = true;
3441 }
3442
3443 if( (curWidth + gutterWidth + imageWidth) < (areaWidth - (nbTnInCurrRow * borderWidth)) ) {
3444 // enough place left in the current row
3445 curWidth += imageWidth + gutterWidth;
3446 rowHeight[rowNum] = tnHeight;
3447
3448 // prevent incomplete row from being heigher than the previous ones.
3449 // by grief-of-these-days
3450 var rowHeightLimit = Math.max(rowHasVertical ? maxRowHeightVertical : 0, rowHasHorizontal ? maxRowHeightHorizontal : 0);
3451 if( rowHeightLimit > 0 ) {
3452 rowHeight[rowNum] = Math.min(rowHeight[rowNum], rowHeightLimit);
3453 }
3454
3455 rowLastItem[rowNum] = i;
3456 }
3457 else {
3458 // new row after current item --> we need to adujet the row height to have enough space for the current thumbnail
3459 curWidth += gutterWidth+imageWidth;
3460 var ratio = (areaWidth - nbTnInCurrRow * borderWidth) / curWidth;
3461 var rH = Math.floor(tnHeight * ratio);
3462 rowHeight[rowNum] = rH;
3463
3464 // save the max row height for each thumb orientation.
3465 // by grief-of-these-days
3466 if( rowHasVertical ) {
3467 maxRowHeightVertical = Math.max( maxRowHeightVertical, rH );
3468 }
3469 if( rowHasHorizontal ) {
3470 maxRowHeightHorizontal = Math.max( maxRowHeightHorizontal, rH );
3471 }
3472
3473 rowLastItem[rowNum] = i;
3474 bNewRow = true;
3475 }
3476 cnt++;
3477 nbTnInCurrRow++;
3478 }
3479 }
3480
3481 rowNum = 0;
3482 curPosY = 0;
3483 lastPosX = 0;
3484 cnt = 0;
3485
3486 G.GOM.lastFullRow = 0; // display at leat 1 row (even if not full)
3487
3488 // second loop --> calculate each thumbnail size
3489 for( var i = 0; i < nbTn ; i++ ) {
3490 var curTn = G.GOM.items[i];
3491 if( curTn.imageWidth > 0 ) {
3492 var imageRatio = curTn.imageWidth / curTn.imageHeight;
3493 var imageWidth = Math.floor( imageRatio * rowHeight[rowNum] ); // border is already NOT included
3494
3495 if( i == rowLastItem[rowNum] ) {
3496 // row last item --> adjust image width because of rounding problems
3497 if( rowLastItem.length != (rowNum+1) ) {
3498 // last item in current row -> use the full remaining width
3499 imageWidth = areaWidth - lastPosX - borderWidth;
3500 }
3501 else {
3502 // very last item (on the last row)
3503 if( (lastPosX + gutterWidth + imageWidth + borderWidth ) > areaWidth ) {
3504 // reduce size if image is wider as the remaining space
3505 imageWidth = areaWidth - lastPosX - borderWidth;
3506 }
3507 }
3508 }
3509
3510 var rh = parseInt( rowHeight[rowNum] );
3511 imageWidth = parseInt( imageWidth );
3512
3513 // thumbnail image size
3514 curTn.resizedContentWidth = imageWidth;
3515 curTn.resizedContentHeight = rh;
3516 // thumbnail position and size
3517 curTn.width = imageWidth + borderWidth;
3518 curTn.height= rh + G.tn.labelHeight.get() + borderHeight;
3519 curTn.row = rowNum;
3520
3521 curTn.top = curPosY;
3522 var x = lastPosX;
3523 if( G.O.RTL) {
3524 x = areaWidth - lastPosX - curTn.width ;
3525 }
3526 curTn.left = x;
3527
3528 lastPosX += curTn.width + gutterWidth;
3529
3530 if( i == rowLastItem[rowNum] ) {
3531 // start a new row
3532 curPosY += curTn.height + gutterHeight;
3533 G.GOM.lastFullRow = rowNum - 1;
3534 rowNum++;
3535 lastPosX = 0;
3536 }
3537 cnt++;
3538 }
3539 else {
3540 return false;
3541 }
3542 }
3543
3544 if( false ) {
3545 var newTop = 0;
3546 if( typeof GOMidx !== 'undefined' ) {
3547 // hover effect on gallery (vs on thumbnail) --> experimental / not used
3548 if( G.GOM.albumIdx != -1 ) {
3549 var hoveredTn = G.GOM.items[GOMidx];
3550 var item = G.I[hoveredTn.thumbnailIdx];
3551
3552 // hovered thumbnail
3553 hoveredTn.width += 40;
3554 hoveredTn.height += 40;
3555 // todo : left
3556
3557 for( var i = 0; i < nbTn ; i++ ) {
3558 var curTn = G.GOM.items[i];
3559 if( curTn.imageWidth > 0 ) {
3560 if( curTn.row == hoveredTn.row ) {
3561 // hovered row
3562 newTop = 40;
3563 if( hoveredTn.thumbnailIdx != curTn.thumbnailIdx ) {
3564 // not hovered thumbnail
3565 // curTn.resizedContentWidth+=10;
3566 // curTn.resizedContentHeight+=20;
3567 // curTn.width+=10;
3568 curTn.top += 30;
3569 curTn.width -= 20;
3570 curTn.height -= 20;
3571 }
3572 }
3573 else {
3574 // not hovered row
3575 if( curTn.row == 0 ) {
3576 // first row
3577 }
3578 else {
3579 curTn.top += newTop;
3580 }
3581 }
3582 }
3583 }
3584 }
3585 }
3586 }
3587
3588 G.GOM.displayArea.width = areaWidth;
3589 return true;
3590 }
3591
3592
3593 //----- MOSAIC LAYOUT
3594 // Grid using a user defined pattern layout
3595 // With this layout, a pattern definition is handeld a row
3596 function GallerySetLayoutMosaic() {
3597 var areaWidth = G.GOM.cache.areaWidth;
3598 var gutterHeight = G.tn.opt.Get('gutterHeight');
3599 var gutterWidth = G.tn.opt.Get('gutterWidth');
3600 var borderWidth = G.tn.borderWidth * 2;
3601 var borderHeight = G.tn.borderHeight * 2;
3602
3603 var nbTn = G.GOM.items.length;
3604 var row = 0;
3605 var h = 0;
3606 var n = 0;
3607
3608
3609 // first loop: evaluate the gallery width based on the first row
3610 var nbCols = 0;
3611 var maxW = 0;
3612 var mosaicPattern = G.tn.settings.getMosaic();
3613 for( var i = 0; i < nbTn ; i++ ) {
3614 var curPatternElt = mosaicPattern[n];
3615
3616 var cLeft = (curPatternElt.c - 1) * G.tn.defaultSize.getOuterWidth() + (curPatternElt.c - 1) * gutterWidth;
3617 var cWidth = curPatternElt.w * G.tn.defaultSize.getOuterWidth() + (curPatternElt.w - 1) * gutterWidth;
3618
3619 maxW=Math.max(maxW, cLeft + cWidth );
3620
3621 nbCols=Math.max(nbCols, (curPatternElt.c - 1) + curPatternElt.w );
3622
3623 n++;
3624 if( n >= mosaicPattern.length ) {
3625 // end of pattern
3626 break;
3627 }
3628 }
3629 var totalGutterWidth = (nbCols - 1) * gutterWidth;
3630 var scaleFactor = Math.min( (areaWidth - totalGutterWidth ) / ( maxW - totalGutterWidth ), 1);
3631
3632 // second loop: position all the thumbnails based on the layout pattern
3633 row = 0;
3634 n = 0;
3635 var mosaicPattern = G.tn.settings.getMosaic();
3636 for( var i = 0; i < nbTn ; i++ ) {
3637 var curTn = G.GOM.items[i];
3638 var curPatternElt = mosaicPattern[n];
3639
3640 curTn.top = (curPatternElt.r - 1) * G.tn.defaultSize.getOuterHeight()*scaleFactor + (curPatternElt.r - 1) * gutterHeight + row * h + (G.tn.labelHeight.get()*(curPatternElt.r-1)) ;
3641 if( row > 0 ) {
3642 curTn.top += gutterHeight;
3643 }
3644
3645 curTn.left = (curPatternElt.c - 1) * G.tn.defaultSize.getOuterWidth()*scaleFactor + (curPatternElt.c - 1) * gutterWidth;
3646
3647 curTn.height = curPatternElt.h * G.tn.defaultSize.getOuterHeight() * scaleFactor + (curPatternElt.h - 1) * gutterHeight + (G.tn.labelHeight.get() * curPatternElt.h);
3648 curTn.resizedContentHeight = curTn.height - G.tn.labelHeight.get() - borderHeight;
3649
3650 curTn.width = curPatternElt.w * G.tn.defaultSize.getOuterWidth()*scaleFactor + (curPatternElt.w - 1) * gutterWidth;
3651 curTn.resizedContentWidth = curTn.width - borderWidth ;
3652
3653 curTn.row = row;
3654 if( row == 0 ) {
3655 h=Math.max(h, curTn.top + curTn.height);
3656 }
3657
3658 n++;
3659 if( n >= mosaicPattern.length ) {
3660 // end pattern -> new line
3661 n = 0;
3662 row++;
3663 }
3664 }
3665
3666 G.GOM.displayArea.width = (maxW - totalGutterWidth) * scaleFactor + totalGutterWidth;
3667 return true;
3668 }
3669
3670
3671
3672 // --- GRID LAYOUT
3673 function GallerySetLayoutGrid() {
3674 var curPosX= 0,
3675 curPosY= 0,
3676 areaWidth= G.GOM.cache.areaWidth,
3677 gutterWidth= 0,
3678 gutterHeight= G.tn.opt.Get('gutterHeight'),
3679 maxCol= NbThumbnailsPerRow(areaWidth),
3680 w= 0,
3681 cols= [],
3682 curCol= 0,
3683 newAreaWidth = areaWidth,
3684 tnWidth= G.tn.defaultSize.getOuterWidth();
3685 var scaleFactor = 1;
3686 var nbTn= G.GOM.items.length;
3687 var borderWidth = G.tn.borderWidth * 2;
3688 var borderHeight =G.tn.borderHeight * 2;
3689
3690 // retrieve gutter width
3691 if( G.O.thumbnailAlignment == 'justified' ) {
3692 maxCol = Math.min( maxCol, nbTn);
3693 gutterWidth = (maxCol==1 ? 0 : (areaWidth-(maxCol*tnWidth))/(maxCol-1));
3694 }
3695 else {
3696 gutterWidth = G.tn.opt.Get('gutterWidth');
3697 }
3698
3699 // first loop to retrieve the real used width of the area (the evaluation is based on the content of the first line)
3700 // Retrieve the real used width of the area (the evaluation is based on the content of the first line)
3701 if( G.O.RTL || G.O.thumbnailAlignment == 'fillWidth' ) {
3702 // scaled --> evaluate scale factor and number of columns
3703 var totalGutterWidth = (maxCol-1) * gutterWidth;
3704 scaleFactor = (areaWidth - totalGutterWidth) / (maxCol*tnWidth);
3705 if( scaleFactor > 1 ) {
3706 maxCol++; // add one column and re-evaluate the scale factor
3707 }
3708 totalGutterWidth = (maxCol-1) * gutterWidth;
3709 scaleFactor = Math.min( (areaWidth - totalGutterWidth) / (maxCol*tnWidth), 1); // no upscale
3710 newAreaWidth = (maxCol*tnWidth) + totalGutterWidth;
3711 }
3712
3713
3714 G.GOM.lastFullRow = 0 ; // display at leat 1 row (even if not full)
3715 var lastPosY = 0;
3716 var row = 0;
3717
3718 tnWidth = tnWidth * scaleFactor;
3719 var contentWidth = tnWidth - borderWidth;
3720 var tnHeight = G.tn.defaultSize.getOuterHeight() * scaleFactor + G.tn.labelHeight.get();
3721 var contentHeight = G.tn.defaultSize.getOuterHeight() * scaleFactor - borderHeight;
3722
3723 // loop to position and to set size of all thumbnails
3724 for( var i = 0; i < nbTn ; i++ ) {
3725 if( curPosY == 0 ) {
3726 curPosX = curCol * (tnWidth + gutterWidth)
3727 cols[curCol] = curPosX;
3728 w = curPosX + tnWidth;
3729 }
3730 else {
3731 curPosX = cols[curCol];
3732 }
3733
3734 var x = curPosX;
3735 if( G.O.RTL ) {
3736 x = parseInt(newAreaWidth) - curPosX - tnWidth;
3737 }
3738
3739 // MANDATORY : set thumbnail position AND size
3740 var curTn=G.GOM.items[i];
3741 curTn.top = curPosY;
3742 curTn.left = x;
3743 curTn.height = tnHeight;
3744 curTn.width = tnWidth;
3745 // image size
3746 if( G.O.thumbnailAlignment == 'fillWidth' ) {
3747 curTn.resizedContentWidth = contentWidth;
3748 curTn.resizedContentHeight = contentHeight;
3749 }
3750 curTn.row = row;
3751 lastPosY = curPosY;
3752
3753 curCol++;
3754 if( curCol >= maxCol ){
3755 // new line
3756 curCol = 0;
3757 curPosY += tnHeight + gutterHeight;
3758 G.GOM.lastFullRow = row;
3759 row++;
3760 }
3761 }
3762 G.GOM.displayArea.width = w;
3763 return true;
3764 }
3765
3766
3767 //----- Display the thumbnails according to the calculated layout
3768 function GalleryDisplayPart1( forceTransition ) {
3769 if( G.CSStransformName == null ) {
3770 G.$E.conTn.css( 'left' , '0px');
3771 }
3772 else {
3773 G.$E.conTn.css( G.CSStransformName , 'none');
3774 }
3775 CacheViewport();
3776 }
3777
3778 function CacheViewport() {
3779 G.GOM.cache.viewport = getViewport();
3780 G.GOM.cache.areaWidth = G.$E.conTnParent.width();
3781 G.GOM.cache.containerOffset = G.$E.conTnParent.offset();
3782 }
3783
3784
3785
3786 function GalleryDisplayPart2( forceTransition ) {
3787
3788 var nbTn = G.GOM.items.length;
3789 G.GOM.itemsDisplayed = 0;
3790 var threshold = 50;
3791 var cnt = 0; // counter for delay between each thumbnail display
3792
3793
3794 GalleryRenderGetInterval();
3795
3796 for( var i = 0; i < nbTn ; i++ ) {
3797 var curTn = G.GOM.items[i];
3798 if( i >= G.GOM.displayInterval.from && cnt < G.GOM.displayInterval.len ) {
3799 curTn.inDisplayArea = true;
3800 if( forceTransition ) {
3801 curTn.neverDisplayed = true;
3802 }
3803 G.GOM.itemsDisplayed++;
3804 cnt++;
3805 }
3806 else{
3807 curTn.inDisplayArea = false;
3808 }
3809 }
3810
3811 // bottom of the gallery (pagination, more button...)
3812 GalleryBottomManage();
3813
3814 var tnToDisplay = [];
3815 var tnToReDisplay = [];
3816
3817 G.GOM.clipArea.top = -1;
3818 cnt = 0 ;
3819 var lastTnIdx = -1;
3820 G.GOM.clipArea.height = 0;
3821 // NOTE: loop always the whole GOM.items --> in case an already displayed thumbnail needs to be removed
3822 for( var i = 0; i < nbTn ; i++ ) {
3823 var curTn = G.GOM.items[i];
3824 if( curTn.inDisplayArea ) {
3825 if( G.GOM.clipArea.top == -1 ) {
3826 G.GOM.clipArea.top = curTn.top;
3827 }
3828 if( (curTn.top - G.GOM.clipArea.top) <= -1 ) {
3829 // with mosaic layout, the first thumbnail may not give the top position
3830 G.GOM.clipArea.top = curTn.top;
3831 }
3832
3833 G.GOM.clipArea.height = Math.max( G.GOM.clipArea.height, curTn.top-G.GOM.clipArea.top + curTn.height);
3834
3835 if( curTn.neverDisplayed ) {
3836 // thumbnail is not displayed -> check if in viewport to display or not
3837 var top = G.GOM.cache.containerOffset.top + (curTn.top - G.GOM.clipArea.top);
3838 // var left=containerOffset.left+curTn.left;
3839 if( (top + curTn.height) >= (G.GOM.cache.viewport.t - threshold) && top <= (G.GOM.cache.viewport.t + G.GOM.cache.viewport.h + threshold) ) {
3840 // build thumbnail
3841 var item = G.I[curTn.thumbnailIdx];
3842 if( item.$elt == null ) {
3843 ThumbnailBuild( item, curTn.thumbnailIdx, i, (i+1) == nbTn );
3844 }
3845 tnToDisplay.push({idx:i, delay:cnt});
3846 cnt++;
3847 }
3848 }
3849 else {
3850 tnToReDisplay.push({idx: i, delay: 0});
3851 }
3852 // G.GOM.itemsDisplayed++;
3853 lastTnIdx = i;
3854 }
3855 else {
3856 curTn.displayed = false;
3857 var item = G.I[curTn.thumbnailIdx];
3858 if( item.$elt != null ){
3859 item.$elt.css({ opacity: 0, display: 'none' });
3860 }
3861 }
3862 }
3863
3864 var areaWidth = G.$E.conTnParent.width();
3865
3866 // set gallery area really used size
3867 // if( G.GOM.displayArea.width != G.GOM.displayAreaLast.width || G.GOM.displayArea.height != G.GOM.displayAreaLast.height ) {
3868 if( G.GOM.displayArea.width != G.GOM.displayAreaLast.width || G.GOM.clipArea.height != G.GOM.displayAreaLast.height ) {
3869 G.$E.conTn.width( G.GOM.displayArea.width ).height( G.GOM.clipArea.height );
3870 G.GOM.displayAreaLast.width = G.GOM.displayArea.width;
3871 G.GOM.displayAreaLast.height = G.GOM.clipArea.height;
3872 // G.GOM.displayAreaLast.height=G.GOM.displayArea.height-G.GOM.clipArea.top;
3873 }
3874
3875 if( areaWidth != G.$E.conTnParent.width() ) {
3876 // gallery area width changed since layout calculation (for example when a scrollbar appeared)
3877 // so we need re-calculate the layout before displaying the thumbnails
3878 G.GOM.cache.areaWidth = G.$E.conTnParent.width();
3879 GallerySetLayout();
3880 GalleryDisplayPart1( forceTransition );
3881 GalleryDisplayPart2( forceTransition );
3882 return;
3883 }
3884
3885 // counter of not displayed images (is displayed on the last thumbnail)
3886 if( G.layout.support.rows ) {
3887 if( G.galleryDisplayMode.Get() == 'ROWS' || (G.galleryDisplayMode.Get() == 'FULLCONTENT' && G.galleryLastRowFull.Get() && G.GOM.lastFullRow != -1) ){
3888 if( lastTnIdx < (nbTn - 1) ) {
3889 G.GOM.lastDisplayedIdxNew = lastTnIdx;
3890 }
3891 else {
3892 G.GOM.lastDisplayedIdxNew =- 1;
3893 }
3894 // remove last displayed counter
3895 if( G.GOM.lastDisplayedIdx != -1 ) {
3896 var item = G.I[G.GOM.items[G.GOM.lastDisplayedIdx].thumbnailIdx];
3897 item.$getElt('.nGY2GThumbnailIconsFullThumbnail').html('');
3898 }
3899 }
3900 }
3901
3902
3903 // batch set position (and display animation) to all thumbnails
3904 // first display newly built thumbnails
3905 var nbBuild = tnToDisplay.length;
3906 for( var i = 0; i < nbBuild ; i++ ) {
3907 // ThumbnailSetPosition(tnToDisplay[i].idx, tnToDisplay[i].delay+10);
3908 ThumbnailSetPosition(tnToDisplay[i].idx, i);
3909 }
3910
3911 // then re-position already displayed thumbnails
3912 var n = tnToReDisplay.length;
3913 for( var i = 0; i < n ; i++ ) {
3914 // ThumbnailSetPosition(tnToReDisplay[i].idx, nbBuild+1);
3915 ThumbnailSetPosition(tnToReDisplay[i].idx, i);
3916 }
3917
3918 if( G.tn.opt.Get('displayTransition') == 'NONE' ) {
3919 G.galleryResizeEventEnabled = true;
3920 // GalleryThumbnailSliderBuildAndStart(); // image slider on last displayed thumbnail
3921 TriggerCustomEvent('galleryDisplayed');
3922 }
3923 else {
3924 setTimeout(function() {
3925 // change value after the end of the display transistion of the newly built thumbnails
3926 G.galleryResizeEventEnabled = true;
3927 // GalleryThumbnailSliderBuildAndStart(); // image slider on last displayed thumbnail
3928 TriggerCustomEvent('galleryDisplayed');
3929 }, nbBuild * G.tn.opt.Get('displayInterval'));
3930 }
3931
3932 }
3933
3934
3935 // Thumbnail: set the new position
3936 function ThumbnailSetPosition( GOMidx, cnt ) {
3937 var newTop= 0;
3938 var curTn= G.GOM.items[GOMidx];
3939 var idx= G.GOM.items[GOMidx].thumbnailIdx;
3940 var item= G.I[idx];
3941
3942 if( curTn.neverDisplayed ) {
3943 // thumbnail is built but has never been displayed (=first display)
3944 var top = curTn.top - G.GOM.clipArea.top;
3945 if( G.tn.opt.Get('stacks') > 0 ) {
3946 // we have stacks -> do not display them here. They will be displayed at the end of the display animation
3947 item.$elt.last().css({ display: 'block'});
3948 item.$elt.css({ top: top , left: curTn.left });
3949 }
3950 else {
3951 item.$elt.css({ display: 'block', top: top , left: curTn.left });
3952 }
3953 newTop=top;
3954
3955 // display the image of the thumbnail when fully loaded
3956 if( G.O.thumbnailWaitImageLoaded === true ) {
3957 var gi_imgLoad = ngimagesLoaded( item.$getElt('.nGY2TnImg2') );
3958 gi_imgLoad.on( 'progress', function( instance, image ) {
3959 if( image.isLoaded ) {
3960 var albumIdx = image.img.getAttribute('data-albumidx');
3961 if( albumIdx == G.GOM.albumIdx ) {
3962 // ignore event if not on current album
3963 var idx = image.img.getAttribute('data-idx');
3964 G.I[idx].ThumbnailImageReveal();
3965 }
3966 }
3967 });
3968 }
3969 // display the thumbnail
3970 ThumbnailAppear(GOMidx, cnt);
3971
3972 curTn.displayed = true;
3973 curTn.neverDisplayed = false;
3974 }
3975 else {
3976 var topOld = G.GOM.cache.containerOffset.top + item.top;
3977 var top = G.GOM.cache.containerOffset.top + (curTn.top - G.GOM.clipArea.top);
3978 newTop = curTn.top - G.GOM.clipArea.top;
3979 var vp = G.GOM.cache.viewport;
3980 if( G.O.thumbnailDisplayOutsideScreen || ( ( (topOld + curTn.height) >= (vp.t - vp.h) && topOld <= (vp.t + vp.h * 4) ) ||
3981 ( (top + curTn.height) >= (vp.t - vp.h) && top <= (vp.t + vp.h * 4) ) ) ) {
3982 // thumbnail positioned in enlarged viewport (viewport + 4 x viewport height) (v1.5: changed from 2 to 4)
3983 if( curTn.displayed ) {
3984 // thumbnail is displayed
3985 if( item.top != curTn.top || item.left != curTn.left ) {
3986 // set position
3987 if( G.O.galleryResizeAnimation == true ) {
3988 // with transition
3989 var tweenable = new NGTweenable();
3990 tweenable.tween({
3991 from: { top: item.top, left: item.left, height: item.height, width: item.width },
3992 to: { top: newTop, left: curTn.left, height: curTn.height, width: curTn.width },
3993 attachment: { $e: item.$elt },
3994 duration: 100,
3995 delay: cnt * G.tn.opt.Get('displayInterval') / 5,
3996 easing: 'easeInOutQuad',
3997 step: function (state, att) {
3998 att.$e.css(state);
3999 },
4000 finish: function (state, att) {
4001 att.$e.css(state);
4002 this.dispose();
4003 }
4004 });
4005 }
4006 else {
4007 // set position without transition
4008 // item.$elt.css({ top: curTn.top , left: curTn.left });
4009 item.$elt.css({ top: newTop , left: curTn.left });
4010 }
4011 }
4012 }
4013 else {
4014 // re-display thumbnail
4015 curTn.displayed = true;
4016 // item.$elt.css({ display: 'block', top: curTn.top , left: curTn.left, opacity:1 });
4017 item.$elt.css({ display: 'block', top: newTop, left: curTn.left, opacity: 1 });
4018 ThumbnailAppearFinish(item);
4019 }
4020 }
4021 else {
4022 // undisplay thumbnail if not in viewport+margin --> performance gain
4023 curTn.displayed = false;
4024 item.$elt.css({ display: 'none'});
4025 }
4026 }
4027 item.left = curTn.left;
4028 item.top = newTop;
4029
4030 // set new size if changed
4031 if( item.width != curTn.width || item.height != curTn.height ) {
4032 item.$elt.css({ width: curTn.width , height: curTn.height });
4033 item.width = curTn.width;
4034 item.height = curTn.height;
4035
4036 // if( curTn.resizedContentWidth > 0 ) {
4037 // resize also the content (=image)
4038 if( item.resizedContentWidth != curTn.resizedContentWidth || item.resizedContentHeight != curTn.resizedContentHeight ) {
4039 if( item.kind == 'albumUp' ) {
4040 // item.$getElt('.nGY2GThumbnailAlbumUp').css({'height': curTn.resizedContentHeight, 'width': curTn.resizedContentWidth});
4041 }
4042 else {
4043 item.$getElt('.nGY2GThumbnailImage').css({'height': curTn.resizedContentHeight, 'width': curTn.resizedContentWidth});
4044
4045 if( G.layout.engine == 'JUSTIFIED' ) {
4046 item.$getElt('.nGY2GThumbnailImg').css({'height': curTn.resizedContentHeight, 'width': curTn.resizedContentWidth});
4047 }
4048 }
4049 item.resizedContentWidth = curTn.resizedContentWidth;
4050 item.resizedContentHeight = curTn.resizedContentHeight;
4051 }
4052 }
4053
4054
4055 // add counter of remaining (not displayed) images
4056 if( G.GOM.lastDisplayedIdxNew == GOMidx && G.layout.support.rows ) {
4057 if( (G.galleryDisplayMode.Get() == 'ROWS' && G.galleryMaxRows.Get() > 0) || (G.galleryDisplayMode.Get() == 'FULLCONTENT' && G.galleryLastRowFull.Get() && G.GOM.lastFullRow != -1) ){
4058 // number of items
4059 var nb = G.GOM.items.length - GOMidx - 1;
4060 if( item.albumID != '0' && G.O.thumbnailLevelUp ) {
4061 nb--;
4062 }
4063
4064 if( nb > 0 ) {
4065 // display counter
4066 if( G.O.thumbnailOpenImage || G.O.thumbnailSliderDelay > 0 ) {
4067 item.$getElt('.nGY2GThumbnailIconsFullThumbnail').html( '+' + nb);
4068 }
4069
4070 // if( G.layout.engine == 'GRID' && G.GOM.slider.hostItem != G.GOM.NGY2Item(GOMidx) ) {
4071 // image slider on last displayed thumbnail
4072 if( G.GOM.slider.hostItem != G.GOM.NGY2Item(GOMidx) ) {
4073
4074 // set current slider back to initial content
4075 GalleryThumbnailSliderSetContent( G.GOM.slider.hostItem );
4076 // new slider
4077 G.GOM.slider.hostIdx = GOMidx;
4078 G.GOM.slider.hostItem = G.GOM.NGY2Item(GOMidx);
4079 G.GOM.slider.nextIdx = GOMidx;
4080 G.GOM.slider.currentIdx = GOMidx;
4081 GalleryThumbnailSliderBuildAndStart(); // image slider on last displayed thumbnail
4082 // GalleryThumbnailSliderSetNextContent();
4083 }
4084 }
4085 else {
4086 // reset slider content to initial content because all thumbnails are displayed
4087 GalleryThumbnailSliderSetContent( G.GOM.slider.hostItem );
4088 G.GOM.slider.hostIdx = -1;
4089 }
4090
4091 G.GOM.lastDisplayedIdx = GOMidx;
4092 }
4093 }
4094
4095 }
4096
4097 // ---------------------
4098 // replace image on last thumbnails with not displayed ones (mode ROWS or FULLCONTENT with galleryLastRowFull enabled)
4099 // function GalleryLastThumbnailSlideImage() {
4100 function GalleryThumbnailSliderBuildAndStart() {
4101
4102 if( G.O.thumbnailSliderDelay == 0 || G.GOM.slider.hostIdx == -1 ) {
4103 return;
4104 }
4105 clearTimeout(G.GOM.slider.timerID);
4106
4107 var item = G.GOM.slider.hostItem;
4108
4109 // dupplicate image layer -> for the next image
4110 if( item.$getElt('.nGY2TnImgNext').length == 0 ) {
4111 item.$getElt('.nGY2TnImg').clone().removeClass('nGY2TnImg').addClass('nGY2TnImgNext').insertAfter(item.$getElt('.nGY2TnImg'));
4112 item.$getElt('.nGY2TnImgBack').clone().removeClass('nGY2TnImgBack').addClass('nGY2TnImgBackNext').insertAfter(item.$getElt('.nGY2TnImg', true));
4113 item.$getElt('.nGY2GThumbnailImage', true); // important -> refresh the cache
4114 item.$getElt('.nGY2GThumbnailImg', true); // important -> refresh the cache
4115 }
4116
4117 item.CSSTransformSet('.nGY2TnImgNext', 'translateX', '100%', true);
4118 item.CSSTransformApply( '.nGY2TnImgNext' );
4119 item.CSSTransformSet('.nGY2TnImgBackNext', 'translateX', '100%', true);
4120 item.CSSTransformApply( '.nGY2TnImgBackNext' );
4121
4122 GalleryThumbnailSliderSetNextContent();
4123
4124 // clearTimeout(G.GOM.slider.timerID);
4125 G.GOM.slider.timerID = setTimeout(function(){ GalleryThumbnailSliderStartTransition() }, G.O.thumbnailSliderDelay);
4126 }
4127
4128
4129 function GalleryThumbnailSliderSetNextContent() {
4130
4131 G.GOM.slider.nextIdx++;
4132 if( G.GOM.slider.nextIdx >= G.GOM.items.length ) {
4133 G.GOM.slider.nextIdx = G.GOM.slider.hostIdx;
4134 }
4135
4136 // new image
4137 var newItem = G.GOM.NGY2Item(G.GOM.slider.nextIdx);
4138 var imgBlurred = G.emptyGif;
4139 var bgImg = "url('" + G.emptyGif + "')";
4140 if( newItem.imageDominantColors != null ) {
4141 imgBlurred = newItem.imageDominantColors;
4142 bgImg = "url('" + newItem.imageDominantColors + "')";
4143 }
4144 G.GOM.slider.hostItem.$getElt('.nGY2TnImgBackNext', true).css({'background-image': bgImg, opacity: 1 });
4145 G.GOM.slider.hostItem.$getElt('.nGY2TnImgNext', true).css({ 'background-image': "url('" + newItem.thumbImg().src + "')", opacity: 1 });
4146 G.GOM.slider.hostItem.$getElt('.nGY2TnImgNext .nGY2GThumbnailImg', true).attr('src', newItem.thumbImg().src );
4147
4148
4149 }
4150
4151 // thumbnail slider - transition from one image to the next one
4152 function GalleryThumbnailSliderStartTransition() {
4153
4154 if( G.GOM.slider.hostItem.$getElt() != null ) {
4155
4156 // slider transition
4157 var tweenable = new NGTweenable();
4158 G.GOM.slider.tween = tweenable;
4159 tweenable.tween({
4160 from: { 'left': 100 },
4161 to: { 'left': 0 },
4162 duration: 800,
4163 delay: 0,
4164 easing: 'easeInOutQuad',
4165
4166 step: function (state) {
4167 if( G.GOM.slider.hostItem.$getElt() == null ) {
4168 // the thumbnail may have been destroyed since the start of the animation
4169 G.GOM.slider.tween.stop(false);
4170 return;
4171 }
4172
4173 // slide current content
4174 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgBack', 'translateX', -(100 - state.left) + '%');
4175 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgBack' );
4176 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImg', 'translateX', -(100 - state.left) + '%');
4177 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImg' );
4178
4179 // slide new content
4180 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgBackNext', 'translateX', state.left + '%');
4181 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgBackNext' );
4182 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgNext', 'translateX', state.left + '%');
4183 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgNext' );
4184
4185 },
4186 finish: function (state) {
4187 if( G.GOM.slider.hostItem.$getElt() == null ) {
4188 // the thumbnail may be destroyed since the start of the animation
4189 return;
4190 }
4191
4192 if( G.GOM.NGY2Item(G.GOM.slider.nextIdx) == null ) { return; } // item does not exist anymore
4193
4194 // set new content as current content
4195 GalleryThumbnailSliderSetContent( G.GOM.NGY2Item(G.GOM.slider.nextIdx) );
4196 G.GOM.slider.currentIdx = G.GOM.slider.nextIdx;
4197 GalleryThumbnailSliderSetNextContent();
4198
4199 clearTimeout(G.GOM.slider.timerID);
4200 G.GOM.slider.timerID=setTimeout(function(){ GalleryThumbnailSliderStartTransition() }, G.O.thumbnailSliderDelay);
4201 }
4202 });
4203 }
4204 }
4205
4206 // set main content of the thumbnail hosting the slider
4207 // hide the elements for the next content of the slider
4208 function GalleryThumbnailSliderSetContent( ngy2itemContent ) {
4209 if( G.GOM.slider.hostIdx == -1 ) { return; }
4210
4211 if( G.GOM.slider.tween != null ) {
4212 if( G.GOM.slider.tween._isTweening == true ) {
4213 G.GOM.slider.tween.stop(false);
4214 }
4215 }
4216
4217 var bgImg = "url('" + G.emptyGif + "')";
4218 if( ngy2itemContent.imageDominantColors != null ) {
4219 bgImg = "url('" + ngy2itemContent.imageDominantColors + "')";
4220 }
4221 G.GOM.slider.hostItem.$getElt('.nGY2TnImgBack').css('background-image', bgImg);
4222 G.GOM.slider.hostItem.$getElt('.nGY2TnImg').css('background-image', "url('" + ngy2itemContent.thumbImg().src + "')" );
4223 G.GOM.slider.hostItem.$getElt('.nGY2TnImg .nGY2GThumbnailImg').attr('src', ngy2itemContent.thumbImg().src );
4224
4225 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgBack', 'translateX', '0');
4226 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgBack' );
4227 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImg', 'translateX', '0');
4228 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImg' );
4229
4230 // place the containers for the next image slider outside of the thumbnail (=hidden)
4231 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgBackNext', 'translateX', '100%', true);
4232 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgBackNext' );
4233 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgNext', 'translateX', '100%', true);
4234 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgNext' );
4235
4236 // set new title and description
4237 if( G.O.thumbnailLabel.get('display') == true ) {
4238 var icons = G.O.icons.thumbnailAlbum;
4239 if( ngy2itemContent.kind != 'album' ) {
4240 icons = G.O.icons.thumbnailImage;
4241 }
4242 G.GOM.slider.hostItem.$getElt('.nGY2GThumbnailTitle').html(icons + getThumbnailTitle(ngy2itemContent));
4243 G.GOM.slider.hostItem.$getElt('.nGY2GThumbnailDescription').html(icons + getTumbnailDescription(ngy2itemContent));
4244 }
4245 }
4246
4247
4248
4249 // Compute the height of the label part of a thumbnail (title+description, both single line)
4250 function ThumbnailGetLabelHeight() {
4251 var newElt = [],
4252 newEltIdx = 0;
4253
4254 // if( G.O.thumbnailLabel.get('display') == false && G.tn.toolbar.getWidth(item) <= 0 ) {
4255 if( G.O.thumbnailLabel.get('display') == false ) {
4256 return 0;
4257 }
4258
4259 var desc='';
4260 if( G.O.thumbnailLabel.get('displayDescription') == true ) {
4261 desc = 'aAzZjJ';
4262 }
4263
4264 // visibility set to hidden
4265 newElt[newEltIdx++] = '<div class="nGY2GThumbnail ' + G.O.theme + '" style="display:block;visibility:hidden;position:absolute;top:-9999px;left:-9999px;" ><div class="nGY2GThumbnailSub">';
4266 if( G.O.thumbnailLabel.get('display') == true ) {
4267 // Labels: title and description
4268 newElt[newEltIdx++] = ' <div class="nGY2GThumbnailLabel" '+ G.tn.style.getLabel() +'>';
4269 newElt[newEltIdx++] = ' <div class="nGY2GThumbnailAlbumTitle" '+G.tn.style.getTitle()+'>aAzZjJ</div>';
4270 if( G.O.thumbnailLabel.get('displayDescription') == true ) {
4271 newElt[newEltIdx++] = ' <div class="nGY2GThumbnailDescription" '+G.tn.style.getDesc()+'>'+'aAzZjJ'+'</div>';
4272 }
4273 newElt[newEltIdx++] = ' </div>';
4274 }
4275
4276 newElt[newEltIdx++] = '</div></div>';
4277
4278 var $newDiv = jQuery(newElt.join('')).appendTo(G.$E.conTn);
4279 var h = $newDiv.find('.nGY2GThumbnailLabel').outerHeight(true);
4280 $newDiv.remove();
4281
4282 return h;
4283 }
4284
4285 function ThumbnailBuildStacks( bgColor ) {
4286 var ns=G.tn.opt.Get('stacks');
4287 if( ns == 0 ) { return ''; }
4288
4289 var s='';
4290 for( var i=0; i<ns; i++ ) {
4291 s='<div class="nGY2GThumbnailStack " style="display:none;'+bgColor+'"></div>'+s;
4292 }
4293 return s;
4294 }
4295
4296 //----- Build one UP thumbnail (=navigation thumbnail)
4297 function ThumbnailBuildAlbumpUp( item, idx, GOMidx ) {
4298 var newElt = [],
4299 newEltIdx = 0;
4300
4301 var mp = '';
4302 if( G.O.thumbnailOpenImage === false ) {
4303 mp = 'cursor:default;'
4304 }
4305
4306 newElt[newEltIdx++] = ThumbnailBuildStacks('') + '<div class="nGY2GThumbnail" style="display:none;opacity:0;' + mp + '" >';
4307 newElt[newEltIdx++] = ' <div class="nGY2GThumbnailSub">';
4308
4309 var h=G.tn.defaultSize.getHeight(),
4310 w=G.tn.defaultSize.getWidth();
4311
4312 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>';
4313 // newElt[newEltIdx++] = ' <div class="nGY2GThumbnailAlbumUp" style="width:'+w+'px;height:'+h+'px;">'+G.O.icons.thumbnailAlbumUp+'</div>';
4314 newElt[newEltIdx++] = ' <div class="nGY2GThumbnailAlbumUp" >'+G.O.icons.thumbnailAlbumUp+'</div>';
4315 newElt[newEltIdx++] = ' </div>';
4316 newElt[newEltIdx++] = '</div>';
4317
4318 var $newDiv = jQuery(newElt.join('')).appendTo(G.$E.conTn); //.animate({ opacity: 1},1000, 'swing'); //.show('slow'); //.fadeIn('slow').slideDown('slow');
4319
4320 item.$elt = $newDiv;
4321 $newDiv.data('index', GOMidx);
4322 item.$getElt('.nGY2GThumbnailImg').data('index', GOMidx);
4323
4324 return;
4325 }
4326
4327
4328 //----- Build one thumbnail
4329 function ThumbnailBuild( item, idx, GOMidx, lastOne ) {
4330 item.eltTransform = [];
4331 item.eltFilter = [];
4332 item.hoverInitDone = false;
4333 item.$Elts = [];
4334
4335 if( item.kind == 'albumUp' ) {
4336 ThumbnailBuildAlbumpUp( item, idx, GOMidx);
4337 return;
4338 }
4339
4340 var newElt = [],
4341 newEltIdx = 0;
4342
4343 var mp = '';
4344 if( G.O.thumbnailOpenImage === false ) {
4345 mp = 'cursor:default;'
4346 }
4347
4348 var src = item.thumbImg().src,
4349 sTitle = getThumbnailTitle(item);
4350
4351 // image background -> visible during image download
4352 var bg = '';
4353 var bgImg = "background-image: url('" + G.emptyGif + "');";
4354 if( item.imageDominantColors != null ) {
4355 // dominant colorS (blurred preview image)
4356 bgImg = "background-image: url('" + item.imageDominantColors + "');";
4357 }
4358 else {
4359 // dominant color -> background color
4360 if( item.imageDominantColor != null ) {
4361 bg = 'background-color:' + item.imageDominantColor + ';';
4362 }
4363 else {
4364 bgImg = '';
4365 }
4366 }
4367
4368 var op = 'opacity:1;';
4369 if( G.O.thumbnailWaitImageLoaded == true ) {
4370 op = 'opacity:0;';
4371 }
4372
4373 // ##### thumbnail containers (with stacks)
4374 newElt[newEltIdx++] = ThumbnailBuildStacks(bg) + '<div class="nGY2GThumbnail" style="display:none;opacity:0;' + mp + '"><div class="nGY2GThumbnailSub ' + ( G.O.thumbnailSelectable && item.selected ? "nGY2GThumbnailSubSelected" : "" ) + '">';
4375
4376
4377 // image size
4378 var w = G.tn.settings.getW();
4379 var h = G.tn.settings.getH();
4380 if( G.tn.settings.getMosaic() !== null ) {
4381 // mosaic layout ->
4382 w = G.GOM.items[GOMidx].width;
4383 h = G.GOM.items[GOMidx].height;
4384 }
4385
4386 var bgSize = 'contain';
4387 if( G.tn.opt.Get('crop') ) {
4388 bgSize = 'cover';
4389 }
4390
4391 // ##### layer for image background (color, dominant color, blurred preview)
4392 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;";
4393 newElt[newEltIdx++]='<div class="nGY2GThumbnailImage nGY2TnImgBack" style="' + s1 + '"></div>';
4394
4395 // #### layer for image
4396 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;";
4397 newElt[newEltIdx++]='<div class="nGY2GThumbnailImage nGY2TnImg" style="' + s2 + '">';
4398 newElt[newEltIdx++]=' <img class="nGY2GThumbnailImg nGY2TnImg2" src="' + src + '" alt="' + sTitle + '" style="opacity:0;" data-idx="' + idx + '" data-albumidx="' + G.GOM.albumIdx + '" >';
4399 newElt[newEltIdx++]='</div>';
4400
4401
4402 // ##### layer for user customization purposes
4403 newElt[newEltIdx++]='<div class="nGY2GThumbnailCustomLayer"></div>';
4404
4405 // ##### layer for labels (title + description and their icons)
4406 if( G.O.thumbnailLabel.get('display') == true ) {
4407 // Labels: title and description
4408 newElt[newEltIdx++]= ' <div class="nGY2GThumbnailLabel" '+ G.tn.style.getLabel(item) + '>';
4409 if( item.kind == 'album' ) {
4410 // album kind
4411 newElt[newEltIdx++]= ' <div class="nGY2GThumbnailTitle nGY2GThumbnailAlbumTitle" ' + G.tn.style.getTitle() + '>' + G.O.icons.thumbnailAlbum + sTitle + '</div>';
4412 }
4413 else {
4414 // image/media kind
4415 newElt[newEltIdx++]= ' <div class="nGY2GThumbnailTitle nGY2GThumbnailImageTitle" ' + G.tn.style.getTitle() + '>' + G.O.icons.thumbnailImage + sTitle + '</div>';
4416 }
4417 newElt[newEltIdx++]= ' <div class="nGY2GThumbnailDescription" ' + G.tn.style.getDesc() + '>' + getTumbnailDescription(item) + '</div>';
4418 newElt[newEltIdx++]= ' </div>';
4419 }
4420
4421 // ##### layer for tools
4422 newElt[newEltIdx++] = ThumbnailBuildTools(item, lastOne);
4423
4424 // close containers
4425 newElt[newEltIdx++]='</div></div>';
4426
4427 var $newDiv =jQuery(newElt.join('')).appendTo(G.$E.conTn);
4428
4429 item.$elt=$newDiv;
4430 $newDiv.data('index',GOMidx);
4431 item.$getElt('.nGY2GThumbnailImg').data('index',GOMidx);
4432
4433 // Custom init function
4434 var fu=G.O.fnThumbnailInit;
4435 if( fu !== null ) {
4436 typeof fu == 'function' ? fu($newDiv, item, GOMidx) : window[fu]($newDiv, item, GOMidx);
4437 }
4438
4439 if( item.title != 'image gallery by nanogallery2 [build]' ) {
4440 ThumbnailOverInit(GOMidx);
4441 }
4442
4443 return ;
4444 }
4445
4446
4447 // Thumbnail layer for tools (toolbars and counter)
4448 function ThumbnailBuildTools( item, lastThumbnail ) {
4449
4450 // toolbars
4451 var tb = ThumbnailBuildToolbarOne(item, 'topLeft') + ThumbnailBuildToolbarOne(item, 'topRight') + ThumbnailBuildToolbarOne(item, 'bottomLeft') + ThumbnailBuildToolbarOne(item, 'bottomRight');
4452
4453 // counter of not displayed images
4454 tb += '<div class="nGY2GThumbnailIconsFullThumbnail"></div>';
4455
4456 return tb;
4457 }
4458
4459 function ThumbnailBuildToolbarOne( item, position ) {
4460 var toolbar = '';
4461 var tb = G.tn.toolbar.get(item);
4462 var width = { xs:0, sm:1, me:2, la:3, xl:4 };
4463 var cnt = 0;
4464
4465 if( tb[position] != '' ) {
4466 var pos='top: 0; right: 0; text-align: right;'; // 'topRight' and default
4467 switch( position ) {
4468 case 'topLeft':
4469 pos = 'top: 0; left: 0; text-align: left;';
4470 break;
4471 case 'bottomRight':
4472 pos = 'bottom: 0; right: 0; text-align: right;';
4473 break;
4474 case 'bottomLeft':
4475 pos = 'bottom: 0; left: 0; text-align: left;';
4476 break;
4477 }
4478
4479 toolbar += ' <ul class="nGY2GThumbnailIcons" style="' + pos + '">';
4480
4481 var icons = tb[position].split(',');
4482 var nb = icons.length;
4483 for( var i = 0; i < nb; i++ ) {
4484 var icon = icons[i].replace(/^\s*|\s*$/, ''); //trim trailing/leading whitespace
4485
4486 var minWidth = icon.substring(0,2).toLowerCase();
4487 var tIcon = icon;
4488 var display = true;
4489 if( /xs|sm|me|la|xl/i.test(minWidth) ) {
4490 // check visbility (depending on screen width)
4491 if( width[minWidth] > width[G.GOM.curWidth] ) {
4492 display = false;
4493 }
4494 tIcon = icon.substring(2);
4495 }
4496
4497 if( display ) {
4498 var sp=(i+1<nb ? '&nbsp;' :'');
4499 switch( tIcon ) {
4500 case 'COUNTER':
4501 if( item.kind == 'album' ) {
4502 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="">';
4503 toolbar += ' <div class="nGY2GThumbnailIconImageCounter"></div>';
4504 toolbar += ' <div class="nGY2GThumbnailIconText">' + G.O.icons.thumbnailCounter+Math.max((item.getContentLength(false)),item.numberItems) + sp + '</div>';
4505 toolbar += ' </li>';
4506 cnt++;
4507 }
4508 break;
4509 case 'COUNTER2':
4510 if( item.kind == 'album' ) {
4511 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="">';
4512 toolbar += ' <div class="nGY2GThumbnailIconTextBadge">' + G.O.icons.thumbnailCounter+Math.max((item.getContentLength(false)),item.numberItems) + sp + '</div>';
4513 toolbar += ' </li>';
4514 cnt++;
4515 }
4516 break;
4517 case 'SHARE':
4518 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="' + tIcon + '">';
4519 toolbar += ' <div>' + G.O.icons.thumbnailShare + '</div>';
4520 toolbar += ' </li>';
4521 cnt++;
4522 break;
4523 case 'DOWNLOAD':
4524 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="' + tIcon + '">';
4525 toolbar += ' <div>' + G.O.icons.thumbnailDownload + '</div>';
4526 toolbar += ' </li>';
4527 cnt++;
4528 break;
4529 case 'INFO':
4530 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="' + tIcon + '">';
4531 toolbar += ' <div>' + G.O.icons.thumbnailInfo + '</div>';
4532 toolbar += ' </li>';
4533 cnt++;
4534 break;
4535 case 'CART':
4536 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="' + tIcon + '">';
4537 toolbar += ' <div>' + G.O.icons.thumbnailCart + '</div>';
4538 toolbar += ' </li>';
4539 cnt++;
4540 break;
4541 case 'DISPLAY':
4542 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="DISPLAY">';
4543 toolbar += ' <div class="nGY2GThumbnailIconImageShare">' + G.O.icons.thumbnailDisplay + '</div>';
4544 toolbar += ' </li>';
4545 cnt++;
4546 break;
4547 case 'CUSTOM1':
4548 case 'CUSTOM2':
4549 case 'CUSTOM3':
4550 case 'CUSTOM4':
4551 case 'CUSTOM5':
4552 case 'CUSTOM6':
4553 case 'CUSTOM7':
4554 case 'CUSTOM8':
4555 case 'CUSTOM9':
4556 case 'CUSTOM10':
4557 var cust = tIcon.replace('CUSTOM', '');
4558 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="' + tIcon.toLowerCase() + '">';
4559 toolbar += ' <div class="nGY2GThumbnailIconImageShare">' + G.O.icons['thumbnailCustomTool' + cust] + '</div>';
4560 toolbar += ' </li>';
4561 cnt++;
4562 break;
4563 case 'FEATURED':
4564 if( item.featured === true ) {
4565 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="">';
4566 toolbar += ' <div class="nGY2GThumbnailIconImageFeatured">' + G.O.icons.thumbnailFeatured + '</div>';
4567 toolbar += ' </li>';
4568 cnt++;
4569 }
4570 break;
4571 case 'SELECT':
4572 if( G.O.thumbnailSelectable == true ) {
4573 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="TOGGLESELECT">';
4574 if( item.selected === true ) {
4575 toolbar += ' <div class="nGY2GThumbnailIconImageSelect nGY2ThumbnailSelected">' + G.O.icons.thumbnailSelected + '</div>';
4576 }
4577 else {
4578 toolbar += ' <div class="nGY2GThumbnailIconImageSelect nGY2ThumbnailUnselected">' + G.O.icons.thumbnailUnselected + '</div>';
4579 }
4580 toolbar += ' </li>';
4581 cnt++;
4582 }
4583 break;
4584 }
4585 }
4586 }
4587 toolbar += ' </ul>';
4588 }
4589
4590 if( cnt > 0 ) {
4591 return toolbar;
4592 }
4593 else {
4594 return '';
4595 }
4596 }
4597
4598 function getThumbnailTitle( item ) {
4599
4600 var sTitle = item.title;
4601 if( G.O.thumbnailLabel.get('display') == true ) {
4602 if( sTitle === undefined || sTitle.length == 0 ) { sTitle = '&nbsp;'; }
4603
4604 if( G.i18nTranslations.thumbnailImageTitle != '' ) {
4605 sTitle = G.i18nTranslations.thumbnailImageTitle;
4606 }
4607 var ml = G.O.thumbnailLabel.get('titleMaxLength');
4608 if( ml > 3 && sTitle.length > ml ){
4609 sTitle = sTitle.substring(0, ml) + '...';
4610 }
4611 }
4612
4613 return sTitle;
4614 }
4615
4616 function getTumbnailDescription( item ) {
4617 var sDesc = '';
4618 if( G.O.thumbnailLabel.get('displayDescription') == true ) {
4619 if( item.kind == 'album' ) {
4620 if( G.i18nTranslations.thumbnailImageDescription != '' ) {
4621 sDesc = G.i18nTranslations.thumbnailAlbumDescription;
4622 }
4623 else {
4624 sDesc = item.description;
4625 }
4626 }
4627 else {
4628 if( G.i18nTranslations.thumbnailImageDescription != '' ) {
4629 sDesc = G.i18nTranslations.thumbnailImageDescription;
4630 }
4631 else {
4632 sDesc = item.description;
4633 }
4634 }
4635 var ml = G.O.thumbnailLabel.get('descriptionMaxLength');
4636 if( ml > 3 && sDesc.length > ml ){
4637 sDesc = sDesc.substring(0, ml) + '...';
4638 }
4639 if( sDesc.length == 0 ) {
4640 sDesc = '&nbsp;';
4641 }
4642 }
4643
4644 return sDesc;
4645 }
4646
4647
4648
4649 // Retrieve the maximum number of thumbnails that fits in one row
4650 function NbThumbnailsPerRow( areaWidth ) {
4651 var tnW = G.tn.defaultSize.getOuterWidth();
4652
4653 var nbMaxTn = 0;
4654 if( G.O.thumbnailAlignment == 'justified' ) {
4655 nbMaxTn = Math.floor((areaWidth)/(tnW));
4656 }
4657 else {
4658 nbMaxTn = Math.floor((areaWidth + G.tn.opt.Get('gutterWidth'))/(tnW + G.tn.opt.Get('gutterWidth')));
4659 }
4660
4661 if( G.O.maxItemsPerLine >0 && nbMaxTn > G.O.maxItemsPerLine ) {
4662 nbMaxTn = G.O.maxItemsPerLine;
4663 }
4664
4665 if( nbMaxTn < 1 ) { nbMaxTn = 1; }
4666
4667 return nbMaxTn
4668 }
4669
4670 // Thumbnail display animation
4671 function ThumbnailAppear( n, cnt ) {
4672 var curTn = G.GOM.items[n];
4673 var item = G.I[curTn.thumbnailIdx];
4674
4675
4676 if( G.tn.opt.Get('displayTransition') == 'NONE' ) {
4677 item.$elt.css({ opacity: 1 });
4678 ThumbnailAppearFinish( item );
4679 }
4680 else {
4681 if( item.$elt == null ) { return; }
4682 var top = G.GOM.cache.containerOffset.top + ( curTn.top - G.GOM.clipArea.top );
4683 var vp = G.GOM.cache.viewport;
4684 if( (top + (curTn.top - G.GOM.clipArea.top)) >= (vp.t - 50) && top <= (vp.t + vp.h + 50) ) {
4685 // display animation only if in the current viewport
4686 var delay = cnt * G.tn.opt.Get('displayInterval');
4687 if( G.tn.opt.Get('displayTransition') == 'CUSTOM' ) {
4688 if( G.GOM.curNavLevel == 'lN' ) {
4689 G.O.fnThumbnailDisplayEffect(item.$elt, item, n, delay);
4690 }
4691 else {
4692 G.O.fnThumbnailL1DisplayEffect(item.$elt, item, n, delay);
4693 }
4694 }
4695 else {
4696 ThumbnailDisplayAnim[G.tn.opt.Get('displayTransition')](item, delay);
4697 }
4698 return;
4699 }
4700 else {
4701 item.$elt.css({ opacity: 1 });
4702 ThumbnailAppearFinish(item);
4703 }
4704 }
4705 }
4706
4707
4708 // displays thumbnail stacks at the end of the display animation
4709 function ThumbnailAppearFinish( item ) {
4710
4711 // add stacks
4712 var ns = G.tn.opt.Get('stacks');
4713 if( ns > 0 ) {
4714 // display stacks
4715 item.$elt.css({ display: 'block'});
4716 var o = 0.9;
4717 // set stack opacity
4718 for( var i = ns-1; i>=0; i-- ) {
4719 item.$elt.eq(i).css('opacity', o);
4720 o = o - 0.2;
4721 }
4722
4723 }
4724 }
4725
4726
4727
4728 var ThumbnailDisplayAnim = {
4729 RANDOMSCALE: function( item, delay ) {
4730
4731 function randomIntFromInterval(min,max) {
4732 return Math.floor(Math.random()*(max-min+1)+min);
4733 }
4734 var scales = [0.95, 1, 1.05, 1.1];
4735 var zi = [1, 2, 3, 4];
4736
4737 var r = randomIntFromInterval(0,3);
4738 while( r == G.GOM.lastRandomValue ) {
4739 r = randomIntFromInterval(0,3);
4740 }
4741 G.GOM.lastRandomValue = r;
4742 var f = scales[r];
4743 // item.$elt.css({ 'z-index': G.GOM.lastZIndex+zi[r], 'box-shadow': '-1px 2px 5px 1px rgba(0, 0, 0, 0.7)' });
4744 item.$elt.css({ 'z-index': G.GOM.lastZIndex+zi[r], 'box-shadow': '0px 0px 5px 3px rgba(0,0,0,0.74)' });
4745
4746 var tweenable = new NGTweenable();
4747 tweenable.tween({
4748 from: { scale: 0.5, opacity:0 },
4749 to: { scale: f, opacity:1 },
4750 attachment: { $e:item.$elt, item: item, tw: tweenable },
4751 delay: delay,
4752 duration: G.tn.opt.Get('displayTransitionDuration'),
4753 easing: { opacity: 'easeOutQuint', scale: G.tn.opt.Get('displayTransitionEasing') },
4754 step: function (state, att) {
4755 if( att.item.$elt === null ) { // the thumbnail may have been destroyed since the start of the animation
4756 att.tw.stop(false);
4757 return;
4758 }
4759 att.$e.css( G.CSStransformName , 'scale('+state.scale+')').css('opacity',state.opacity);
4760 },
4761 finish: function (state, att) {
4762 if( att.item.$elt === null ) { return; }
4763 att.$e.css( G.CSStransformName , 'scale('+state.scale+')').css('opacity', '');
4764 ThumbnailAppearFinish(att.item);
4765 }
4766 });
4767 },
4768
4769 SCALEUP: function( item, delay ) {
4770 var f = G.tn.opt.Get('displayTransitionStartVal');
4771 if( f == 0 ) { f = 0.6; } // default value
4772
4773 var tweenable = new NGTweenable();
4774 tweenable.tween({
4775 from: { scale: f, opacity: 0 },
4776 to: { scale: 1, opacity: 1 },
4777 attachment: { $e: item.$elt, item: item, tw: tweenable },
4778 delay: delay,
4779 duration: G.tn.opt.Get('displayTransitionDuration'),
4780 easing: { opacity: 'easeOutQuint', scale: G.tn.opt.Get('displayTransitionEasing') },
4781 step: function (state, att) {
4782 if( att.item.$elt === null ) { // the thumbnail may have been destroyed since the start of the animation
4783 att.tw.stop(false);
4784 return;
4785 }
4786 att.$e.css( G.CSStransformName , 'scale('+state.scale+')').css('opacity',state.opacity);
4787 },
4788 finish: function (state, att) {
4789 if( att.item.$elt === null ) { return; }
4790 att.$e.css( G.CSStransformName , '').css('opacity', '');
4791 ThumbnailAppearFinish(att.item);
4792 }
4793 });
4794 },
4795
4796 SCALEDOWN: function( item, delay ) {
4797 var f = G.tn.opt.Get('displayTransitionStartVal');
4798 if( f == 0 ) { f=1.3; } // default value
4799
4800 var tweenable = new NGTweenable();
4801 tweenable.tween({
4802 from: { scale: f, opacity: 0 },
4803 to: { scale: 1, opacity: 1 },
4804 attachment: { item: item, tw: tweenable },
4805 delay: delay,
4806 duration: G.tn.opt.Get('displayTransitionDuration'),
4807 easing: { opacity: 'easeOutQuint', scale: G.tn.opt.Get('displayTransitionEasing') },
4808 step: function (state, att) {
4809 if( att.item.$elt === null ) { // the thumbnail may have been destroyed since the start of the animation
4810 att.tw.stop(false);
4811 return;
4812 }
4813 att.item.$elt.last().css('opacity', state.opacity);
4814 att.item.CSSTransformSet('.nGY2GThumbnail', 'scale', state.scale);
4815 att.item.CSSTransformApply('.nGY2GThumbnail');
4816 },
4817 finish: function (state, att) {
4818 if( att.item.$elt === null ) { return; }
4819 att.item.$elt.last().css('opacity', '');
4820 att.item.CSSTransformSet('.nGY2GThumbnail', 'scale', state.scale);
4821 att.item.CSSTransformApply('.nGY2GThumbnail');
4822 ThumbnailAppearFinish(att.item);
4823 }
4824 });
4825 },
4826
4827 SLIDEUP: function( item, delay ) {
4828 var f = G.tn.opt.Get('displayTransitionStartVal');
4829 if( f == 0 ) { f=50; } // default value
4830 var tweenable = new NGTweenable();
4831 tweenable.tween({
4832 from: { 'opacity': 0, translateY: f },
4833 to: { 'opacity': 1, translateY: 0 },
4834 attachment: { item: item, tw: tweenable },
4835 delay: delay,
4836 duration: G.tn.opt.Get('displayTransitionDuration'),
4837 easing: { opacity: 'easeOutQuint', scale: 'easeOutQuart', translateY: G.tn.opt.Get('displayTransitionEasing') },
4838 step: function (state, att) {
4839 if( att.item.$elt === null ) { // the thumbnail may have been destroyed since the start of the animation
4840 att.tw.stop(false);
4841 return;
4842 }
4843 att.item.$elt.css('opacity', state.opacity);
4844 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px, '+state.translateY + 'px');
4845 att.item.CSSTransformApply('.nGY2GThumbnail');
4846 },
4847 finish: function (state, att) {
4848 if( att.item.$elt === null ) { return; }
4849 this._step(state, att);
4850 att.item.$elt.css('opacity', '');
4851 ThumbnailAppearFinish(att.item);
4852 }
4853 });
4854 },
4855
4856 SLIDEDOWN: function( item, delay ) {
4857 var f=G.tn.opt.Get('displayTransitionStartVal');
4858 if( f == 0 ) { f=-50; } // default value
4859 var tweenable = new NGTweenable();
4860 tweenable.tween({
4861 from: { opacity: 0, translateY: f },
4862 to: { opacity: 1, translateY: 0 },
4863 attachment: { item: item, tw: tweenable },
4864 delay: delay,
4865 duration: G.tn.opt.Get('displayTransitionDuration'),
4866 easing: { opacity: 'easeOutQuint', scale: 'easeOutQuart', translateY: G.tn.opt.Get('displayTransitionEasing') },
4867 step: function (state, att) {
4868 if( att.item.$elt === null ) { // the thumbnail may have been destroyed since the start of the animation
4869 att.tw.stop(false);
4870 return;
4871 }
4872 att.item.$elt.css('opacity', state.opacity);
4873 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px,'+state.translateY+'px');
4874 att.item.CSSTransformApply('.nGY2GThumbnail');
4875 },
4876 finish: function (state, att) {
4877 if( att.item.$elt === null ) { return; }
4878 this._step(state, att);
4879 att.item.$elt.css('opacity', '');
4880 ThumbnailAppearFinish(att.item);
4881 }
4882 });
4883 },
4884
4885 FLIPUP: function( item, delay ) {
4886 var f=G.tn.opt.Get('displayTransitionStartVal');
4887 if( f == 0 ) { f=100; } // default value
4888 var tweenable = new NGTweenable();
4889 tweenable.tween({
4890 from: { opacity: 0, translateY: f, rotateX: 45 },
4891 to: { opacity: 1, translateY: 0, rotateX: 0 },
4892 attachment: { item: item, tw: tweenable },
4893 delay: delay,
4894 duration: G.tn.opt.Get('displayTransitionDuration'),
4895 easing: { opacity: 'easeOutQuint', scale: 'easeOutQuart', translateY: G.tn.opt.Get('displayTransitionEasing') },
4896 step: function (state, att) {
4897 if( att.item.$elt === null ) { // the thumbnail may have been destroyed since the start of the animation
4898 att.tw.stop(false);
4899 return;
4900 }
4901 att.item.$elt.css('opacity', state.opacity);
4902 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px,'+state.translateY+'px');
4903 att.item.CSSTransformSet('.nGY2GThumbnail', 'rotateX', state.rotateX+'deg');
4904 att.item.CSSTransformApply('.nGY2GThumbnail');
4905 },
4906 finish: function (state, att) {
4907 if( att.item.$elt === null ) { return; }
4908 this._step(state, att);
4909 att.item.$elt.css('opacity', '');
4910 ThumbnailAppearFinish(att.item);
4911 }
4912 });
4913 },
4914 FLIPDOWN: function( item, delay ) {
4915 var f=G.tn.opt.Get('displayTransitionStartVal');
4916 if( f == 0 ) { f=-100; } // default value
4917 var tweenable = new NGTweenable();
4918 tweenable.tween({
4919 from: { opacity: 0, translateY: f, rotateX: -45 },
4920 to: { opacity: 1, translateY: 0, rotateX: 0 },
4921 attachment: { item: item, tw: tweenable },
4922 delay: delay,
4923 duration: G.tn.opt.Get('displayTransitionDuration'),
4924 easing: { opacity: 'easeOutQuint', scale:'easeOutQuart', translateY: G.tn.opt.Get('displayTransitionEasing')},
4925 step: function (state, att) {
4926 if( att.item.$elt === null ) { // the thumbnail may have been destroyed since the start of the animation
4927 att.tw.stop(false);
4928 return;
4929 }
4930 att.item.$elt.css('opacity', state.opacity);
4931 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px,' + state.translateY + 'px');
4932 att.item.CSSTransformSet('.nGY2GThumbnail', 'rotateX', state.rotateX + 'deg');
4933 att.item.CSSTransformApply('.nGY2GThumbnail');
4934 },
4935 finish: function (state, att) {
4936 if( att.item.$elt === null ) { return; }
4937 this._step(state, att);
4938 att.item.$elt.css('opacity', '');
4939 ThumbnailAppearFinish(att.item);
4940 }
4941 });
4942 },
4943
4944 SLIDEUP2: function( item, delay ) {
4945 var f=G.tn.opt.Get('displayTransitionStartVal');
4946 if( f == 0 ) { f=100; } // default value
4947 var tweenable = new NGTweenable();
4948 tweenable.tween({
4949 from: { opacity: 0, translateY: f, rotateY: 40 },
4950 to: { opacity: 1, translateY: 0, rotateY: 0 },
4951 attachment: { item: item, tw: tweenable },
4952 delay: delay,
4953 duration: G.tn.opt.Get('displayTransitionDuration'),
4954 easing: { opacity: 'easeOutQuint', scale:'easeOutQuart', translateY: G.tn.opt.Get('displayTransitionEasing') },
4955 step: function (state, att) {
4956 if( att.item.$elt === null ) { // the thumbnail may have been destroyed since the start of the animation
4957 att.tw.stop(false);
4958 return;
4959 }
4960 att.item.$elt.css('opacity', state.opacity);
4961 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px,' + state.translateY + 'px');
4962 att.item.CSSTransformSet('.nGY2GThumbnail', 'rotateY', state.rotateY + 'deg');
4963 att.item.CSSTransformApply('.nGY2GThumbnail');
4964 },
4965 finish: function (state, att) {
4966 if( att.item.$elt === null ) { return; }
4967 this._step(state, att);
4968 att.item.$elt.css('opacity', '');
4969 ThumbnailAppearFinish(att.item);
4970 }
4971 });
4972 },
4973 SLIDEDOWN2: function( item, delay ) {
4974 var f=G.tn.opt.Get('displayTransitionStartVal');
4975 if( f == 0 ) { f=-100; } // default value
4976 var tweenable = new NGTweenable();
4977 tweenable.tween({
4978 from: { opacity: 0, translateY: f, rotateY: 40 },
4979 to: { opacity: 1, translateY: 0, rotateY: 0 },
4980 attachment: { item: item, tw: tweenable },
4981 delay: delay,
4982 duration: G.tn.opt.Get('displayTransitionDuration'),
4983 easing: { opacity: 'easeOutQuint', scale: 'easeOutQuart', translateY: G.tn.opt.Get('displayTransitionEasing') },
4984 step: function (state, att) {
4985 if( att.item.$elt === null ) { // the thumbnail may have been destroyed since the start of the animation
4986 att.tw.stop(false);
4987 return;
4988 }
4989 att.item.$elt.css('opacity', state.opacity);
4990 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px, ' + state.translateY + 'px');
4991 att.item.CSSTransformSet('.nGY2GThumbnail', 'rotateY', state.rotateY + 'deg');
4992 att.item.CSSTransformApply('.nGY2GThumbnail');
4993 },
4994 finish: function (state, att) {
4995 this._step(state, att);
4996 att.item.$elt.css('opacity', '');
4997 att.item.CSSTransformApply('.nGY2GThumbnail');
4998 ThumbnailAppearFinish(att.item);
4999 }
5000 });
5001 },
5002 SLIDERIGHT: function( item, delay ) {
5003 var f=G.tn.opt.Get('displayTransitionStartVal');
5004 if( f == 0 ) { f=-150; } // default value
5005 var tweenable = new NGTweenable();
5006 tweenable.tween({
5007 from: { opacity: 0, translateX: f },
5008 to: { opacity: 1, translateX: 0 },
5009 attachment: { item: item, tw: tweenable },
5010 delay: delay,
5011 duration: G.tn.opt.Get('displayTransitionDuration'),
5012 easing: { opacity: 'easeOutQuint', translateX: G.tn.opt.Get('displayTransitionEasing') },
5013 step: function (state, att) {
5014 if( att.item.$elt === null ) { // the thumbnail may have been destroyed since the start of the animation
5015 att.tw.stop(false);
5016 return;
5017 }
5018 att.item.$elt.css('opacity', state.opacity);
5019 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', state.translateX + 'px, 0px');
5020 att.item.CSSTransformApply('.nGY2GThumbnail');
5021 },
5022 finish: function (state, att) {
5023 if( att.item.$elt === null ) { return; }
5024 this._step(state, att);
5025 att.item.$elt.css('opacity', '');
5026 ThumbnailAppearFinish(att.item);
5027 }
5028 });
5029 },
5030 SLIDELEFT: function( item, delay ) {
5031 var f=G.tn.opt.Get('displayTransitionStartVal');
5032 if( f == 0 ) { f=150; } // default value
5033 var tweenable = new NGTweenable();
5034 tweenable.tween({
5035 from: { opacity: 0, translateX: f },
5036 to: { opacity: 1, translateX: 0 },
5037 attachment: { item: item, tw: tweenable },
5038 delay: delay,
5039 duration: G.tn.opt.Get('displayTransitionDuration'),
5040 easing: { opacity: 'easeOutQuint', translateX: G.tn.opt.Get('displayTransitionEasing') },
5041 step: function (state, att) {
5042 if( att.item.$elt === null ) { // the thumbnail may have been destroyed since the start of the animation
5043 att.tw.stop(false);
5044 return;
5045 }
5046 att.item.$elt.css('opacity', state.opacity);
5047 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', state.translateX + 'px, 0px');
5048 att.item.CSSTransformApply('.nGY2GThumbnail');
5049 },
5050 finish: function (state, att) {
5051 if( att.item.$elt === null ) { return; }
5052 this._step(state, att);
5053 att.item.$elt.css('opacity', '');
5054 ThumbnailAppearFinish(att.item);
5055 }
5056 });
5057 },
5058
5059 FADEIN: function( item, delay ) {
5060 var tweenable = new NGTweenable();
5061 tweenable.tween({
5062 from: { 'opacity': 0 },
5063 to: { 'opacity': 1 },
5064 attachment: { $e:item.$elt, item: item, tw: tweenable },
5065 delay: delay,
5066 duration: G.tn.opt.Get('displayTransitionDuration'),
5067 easing: 'easeInOutSine',
5068 step: function (state, att) {
5069 if( att.item.$elt === null ) { // the thumbnail may have been destroyed since the start of the animation
5070 att.tw.stop(false);
5071 return;
5072 }
5073 att.$e.css(state);
5074 },
5075 finish: function (state, att) {
5076 if( att.item.$elt === null ) { return; }
5077 att.$e.css('opacity', '');
5078 ThumbnailAppearFinish(att.item);
5079 }
5080 });
5081 }
5082 }
5083
5084
5085
5086 // ######################################
5087 // Gallery display animation
5088 function GalleryAppear() {
5089
5090 var d=G.galleryDisplayTransitionDuration.Get();
5091 switch( G.galleryDisplayTransition.Get() ){
5092 case 'ROTATEX':
5093 G.$E.base.css({ perspective: '1000px', 'perspective-origin': '50% 0%' });
5094 var tweenable = new NGTweenable();
5095 tweenable.tween({
5096 from: { r: 50 },
5097 to: { r: 0 },
5098 attachment: { orgIdx: G.GOM.albumIdx },
5099 duration: d,
5100 easing: 'easeOutCirc',
5101 step: function (state, att) {
5102 if( att.orgIdx == G.GOM.albumIdx ) {
5103 G.$E.conTnParent.css( G.CSStransformName , 'rotateX(' + state.r + 'deg)');
5104 }
5105 }
5106 });
5107 break;
5108 case 'SLIDEUP':
5109 G.$E.conTnParent.css({ opacity: 0 });
5110 var tweenable = new NGTweenable();
5111 tweenable.tween({
5112 from: { y: 200, o: 0 },
5113 to: { y: 0, o: 1 },
5114 attachment: { orgIdx: G.GOM.albumIdx },
5115 duration: d,
5116 easing: 'easeOutCirc',
5117 step: function (state, att) {
5118 if( att.orgIdx == G.GOM.albumIdx ) {
5119 G.$E.conTnParent.css( G.CSStransformName , 'translate( 0px, '+state.y + 'px)').css('opacity', state.o);
5120 }
5121 }
5122 });
5123 break;
5124 case 'NONE':
5125 default:
5126 break;
5127 }
5128
5129
5130 }
5131
5132 // ######################################
5133 // ##### THUMBNAIL HOVER MANAGEMENT #####
5134 // ######################################
5135
5136 function ThumbnailOverInit( GOMidx ) {
5137 // Over init in 2 step:
5138 // 1) init with thumbnailBuildInit2 parameter
5139 // 2) init with the hover effect parameter
5140
5141
5142 var curTn = G.GOM.items[GOMidx];
5143 var item = G.I[curTn.thumbnailIdx];
5144
5145 if( item.$elt == null ) { return; } // zombie
5146
5147 var fu = G.O.fnThumbnailHoverInit;
5148 if( fu !== null ) {
5149 typeof fu == 'function' ? fu($e, item, GOMidx) : window[fu]($e, item, GOMidx);
5150 }
5151
5152 // build initialization
5153 var inits = G.tn.buildInit.get();
5154 for( var j = 0; j < inits.length; j++) {
5155 switch( inits[j].property ) {
5156 // CSS Transform
5157 case 'scale':
5158 case 'rotateX':
5159 case 'rotateY':
5160 case 'rotateZ':
5161 case 'translateX':
5162 case 'translateY':
5163 case 'translateZ':
5164 item.CSSTransformSet(inits[j].element, inits[j].property, inits[j].value);
5165 item.CSSTransformApply(inits[j].element);
5166 break;
5167 // CSS filter
5168 case 'blur':
5169 case 'brightness':
5170 case 'grayscale':
5171 case 'sepia':
5172 case 'contrast':
5173 case 'opacity':
5174 case 'saturate':
5175 item.CSSFilterSet(inits[j].element, inits[j].property, inits[j].value);
5176 item.CSSFilterApply(inits[j].element);
5177 break;
5178 default:
5179 var $t=item.$getElt(inits[j].element);
5180 $t.css( inits[j].property, inits[j].value );
5181 break;
5182 }
5183 }
5184
5185 // hover
5186 var effects = G.tn.hoverEffects.get();
5187 for( var j = 0; j < effects.length; j++) {
5188 if( effects[j].firstKeyframe === true ) {
5189 switch( effects[j].type ) {
5190 case 'scale':
5191 case 'rotateX':
5192 case 'rotateY':
5193 case 'rotateZ':
5194 case 'translateX':
5195 case 'translateY':
5196 case 'translateZ':
5197 item.CSSTransformSet(effects[j].element, effects[j].type, effects[j].from);
5198 item.CSSTransformApply(effects[j].element);
5199 break;
5200 case 'blur':
5201 case 'brightness':
5202 case 'grayscale':
5203 case 'sepia':
5204 case 'contrast':
5205 case 'opacity':
5206 case 'saturate':
5207 item.CSSFilterSet(effects[j].element, effects[j].type, effects[j].from);
5208 item.CSSFilterApply(effects[j].element);
5209 break;
5210 default:
5211 var $t = item.$getElt(effects[j].element);
5212 $t.css( effects[j].type, effects[j].from );
5213 break;
5214
5215 }
5216 }
5217 }
5218 item.hoverInitDone=true;
5219 }
5220
5221 function ThumbnailHoverReInitAll() {
5222 if( G.GOM.albumIdx == -1 ) { return; };
5223 var l = G.GOM.items.length;
5224 for( var i = 0; i < l ; i++ ) {
5225 ThumbnailOverInit(i);
5226 // G.GOM.items[i].hovered=false;
5227 G.I[G.GOM.items[i].thumbnailIdx].hovered = false;
5228 }
5229 }
5230
5231
5232 function ThumbnailHover( GOMidx ) {
5233 if( G.GOM.albumIdx == -1 || !G.galleryResizeEventEnabled ) { return; };
5234 if( G.GOM.slider.hostIdx == GOMidx ) {
5235 // slider hosted on thumbnail -> no hover effect
5236 return;
5237 }
5238 var curTn = G.GOM.items[GOMidx];
5239 var item = G.I[curTn.thumbnailIdx];
5240 if( item.kind == 'albumUp' || item.$elt == null ) { return; }
5241
5242 item.hovered = true;
5243
5244 var fu = G.O.fnThumbnailHover;
5245 if( fu !== null ) {
5246 typeof fu == 'function' ? fu(item.$elt, item, GOMidx) : window[fu](item.$elt, item, GOMidx);
5247 }
5248 var effects = G.tn.hoverEffects.get();
5249
5250 try {
5251 for( var j = 0; j < effects.length; j++) {
5252 if( effects[j].hoverin === true ) {
5253 //item.animate( effects[j], j*10, true );
5254 item.animate( effects[j], 0, true );
5255 }
5256 }
5257 // effects on whole layout
5258 // GalleryResize( GOMidx );
5259 }
5260 catch (e) {
5261 NanoAlert(G, 'error on hover: ' + e.message );
5262 }
5263
5264 }
5265
5266 function ThumbnailHoverOutAll() {
5267 if( G.GOM.albumIdx == -1 ) { return; };
5268 var l = G.GOM.items.length;
5269 for( var i = 0; i < l ; i++ ) {
5270 if( G.GOM.items[i].inDisplayArea ) {
5271 ThumbnailHoverOut(i);
5272 }
5273 else {
5274 G.I[G.GOM.items[i].thumbnailIdx].hovered = false;
5275 }
5276 }
5277 }
5278
5279
5280 function ThumbnailHoverOut( GOMidx ) {
5281 if( G.GOM.albumIdx == -1 || !G.galleryResizeEventEnabled ) { return; }
5282
5283 if( G.GOM.slider.hostIdx == GOMidx ) {
5284 // slider on thumbnail -> no hover effect
5285 return;
5286 }
5287
5288 var curTn = G.GOM.items[GOMidx];
5289 var item = G.I[curTn.thumbnailIdx];
5290 if( item.kind == 'albumUp' || !item.hovered ) { return; }
5291 item.hovered = false;
5292 if( item.$elt == null ) { return; }
5293
5294 var fu = G.O.fnThumbnailHoverOut;
5295 if( fu !== null ) {
5296 typeof fu == 'function' ? fu(item.$elt, item, GOMidx) : window[fu](item.$elt, item, GOMidx);
5297 }
5298
5299 var effects = G.tn.hoverEffects.get();
5300 try {
5301 for( var j = 0; j < effects.length; j++) {
5302 if( effects[j].hoverout === true ) {
5303 // item.animate( effects[j], j*10, false );
5304 item.animate( effects[j], 0, false );
5305 }
5306 }
5307 // effects on whole layout
5308 // GalleryResize( );
5309 }
5310 catch (e) {
5311 NanoAlert(G, 'error on hoverOut: ' + e.message );
5312 }
5313
5314 }
5315
5316
5317 /** @function DisplayPhoto */
5318 function DisplayPhoto( imageID, albumID ) {
5319
5320 if( G.O.debugMode ) { console.log('#DisplayPhoto : '+ albumID +'-'+ imageID); }
5321 var albumIdx = NGY2Item.GetIdx(G, albumID);
5322 if( albumIdx == 0 ) {
5323 G.GOM.curNavLevel='l1';
5324 }
5325 else {
5326 G.GOM.curNavLevel='lN';
5327 }
5328
5329 if( albumIdx == -1 ) {
5330 // get content of album on root level
5331 if( G.O.kind != '' ) {
5332 // do not add adlbum if Markup or Javascript data
5333 NGY2Item.New( G, '', '', albumID, '0', 'album' ); // create empty album
5334 albumIdx=G.I.length-1;
5335 }
5336 }
5337
5338 var ngy2ItemIdx = NGY2Item.GetIdx(G, imageID);
5339 if( ngy2ItemIdx == -1 ) {
5340 // get content of the album
5341 AlbumGetContent( albumID, DisplayPhoto, imageID, albumID );
5342 return;
5343 }
5344
5345 if( G.O.debugMode ) { console.log('#DisplayPhoto : '+ ngy2ItemIdx); }
5346
5347 DisplayPhotoIdx(ngy2ItemIdx);
5348
5349 }
5350
5351
5352 // BETA -> NOT finished and not used at this time
5353 // Retrieve the title+description of ONE album
5354 function albumGetInfo( albumIdx, fnToCall ) {
5355 var url = '';
5356 var kind = 'image';
5357
5358 switch( G.O.kind ) {
5359 case 'json':
5360 // TODO
5361 case 'flickr':
5362 // TODO
5363 case 'picasa':
5364 case 'google':
5365 case 'google2':
5366 default:
5367 url = G.Google.url() + 'user/'+G.O.userID+'/albumid/'+G.I[albumIdx].GetID()+'?alt=json&&max-results=1&fields=title';
5368 break;
5369 }
5370
5371 jQuery.ajaxSetup({ cache: false });
5372 jQuery.support.cors = true;
5373
5374 var tId = setTimeout( function() {
5375 // workaround to handle JSONP (cross-domain) errors
5376 //PreloaderHide();
5377 NanoAlert(G, 'Could not retrieve AJAX data...');
5378 }, 60000 );
5379 jQuery.getJSON(url, function(data, status, xhr) {
5380 clearTimeout(tId);
5381 //PreloaderHide();
5382
5383 fnToCall( G.I[albumIdx].GetID() );
5384
5385 })
5386 .fail( function(jqxhr, textStatus, error) {
5387 clearTimeout(tId);
5388 //PreloaderHide();
5389 var err = textStatus + ', ' + error;
5390 NanoAlert('Could not retrieve ajax data: ' + err);
5391 });
5392
5393 }
5394
5395
5396 // function AlbumGetContent( albumIdx, fnToCall ) {
5397 function AlbumGetContent( albumID, fnToCall, fnParam1, fnParam2 ) {
5398 // var url='';
5399 // var kind='image';
5400 // var albumIdx=NGY2Item.GetIdx(G, albumID);
5401 // var photoIdx=NGY2Item.GetIdx(G, photoID);
5402
5403 switch( G.O.kind ) {
5404 // MARKUP / API
5405 case '':
5406 AlbumGetMarkupOrApi(fnToCall, fnParam1, fnParam2);
5407 break;
5408 // JSON, Flickr, Picasa, ...
5409 default:
5410 jQuery.nanogallery2['data_'+G.O.kind](G, 'AlbumGetContent', albumID, fnToCall, fnParam1, fnParam2 );
5411 }
5412
5413 }
5414
5415 var mediaList = {
5416 youtube : {
5417 getID: function( url ) {
5418 // https://stackoverflow.com/questions/10591547/how-to-get-youtube-video-id-from-url
5419 var s = url.match( /(?:https?:\/{2})?(?:w{3}\.)?youtu(?:be)?\.(?:com|be)(?:\/watch\?v=|\/)([^\s&]+)/ );
5420 return s != null ? s[1] : null;
5421 },
5422 thumbUrl: function( id ) {
5423 return 'https://img.youtube.com/vi/' + id + '/hqdefault.jpg';
5424 },
5425 url: function( id ) {
5426 return 'https://www.youtube.com/embed/' + id;
5427 },
5428 markup: function( id ) {
5429 return '<iframe class="nGY2ViewerMedia" src="https://www.youtube.com/embed/' + id + '?rel=0" frameborder="0" gesture="media" allowfullscreen></iframe>';
5430 },
5431 kind: 'iframe'
5432 },
5433 vimeo : {
5434 getID: function( url ) {
5435 // https://stackoverflow.com/questions/2916544/parsing-a-vimeo-id-using-javascript
5436 var s = url.match( /^.*(vimeo\.com\/)((channels\/[A-z]+\/)|(groups\/[A-z]+\/videos\/))?([0-9]+)/ );
5437 return s != null ? s[5] : null;
5438 },
5439 url: function( id ) {
5440 return 'https://player.vimeo.com/video/' + id;
5441 },
5442 markup: function( id ) {
5443 return '<iframe class="nGY2ViewerMedia" src="https://player.vimeo.com/video/' + id + '?rel=0" frameborder="0" gesture="media" allowfullscreen></iframe>';
5444 },
5445 kind: 'iframe'
5446 },
5447 dailymotion : {
5448 getID: function( url ) {
5449 // https://stackoverflow.com/questions/12387389/how-to-parse-dailymotion-video-url-in-javascript
5450 var m = url.match(/^.+dailymotion.com\/(video|hub)\/([^_]+)[^#]*(#video=([^_&]+))?/);
5451 if (m !== null) {
5452 if(m[4] !== undefined) {
5453 return m[4];
5454 }
5455 return m[2];
5456 }
5457 return null;
5458 },
5459 thumbUrl: function( id ) {
5460 return 'https://www.dailymotion.com/thumbnail/video/' + id;
5461 },
5462 url: function( id ) {
5463 return 'https://www.dailymotion.com/embed/video/' + id;
5464 },
5465 markup: function( id ) {
5466 return '<iframe class="nGY2ViewerMedia" src="https://www.dailymotion.com/embed/video/' + id + '?rel=0" frameborder="0" gesture="media" allowfullscreen></iframe>';
5467 },
5468 kind: 'iframe'
5469 }
5470 };
5471
5472 function AlbumGetMarkupOrApi ( fnToCall, fnParam1, fnParam2 ) {
5473
5474 if( G.markupOrApiProcessed === true ) {
5475 // already processed (maybe location hash to unknow reference) -> display root album
5476 DisplayAlbum('-1', 0);
5477 return;
5478 }
5479
5480 if( G.O.items !== undefined && G.O.items !== null ) {
5481 // data defined as an object in an option parameter
5482 GetContentApiObject();
5483 }
5484 else {
5485 if( G.O.$markup.length > 0 ) {
5486 // data defined as markup (href elements)
5487 GetContentMarkup( G.O.$markup );
5488 G.O.$markup=[] ;
5489 }
5490 else {
5491 NanoConsoleLog(G, 'error: no image to process.');
5492 return;
5493 }
5494 }
5495
5496 G.markupOrApiProcessed = true;
5497 if( fnToCall !== null && fnToCall !== undefined) {
5498 fnToCall( fnParam1, fnParam2, null );
5499 }
5500 }
5501
5502 function StartsWithProtocol ( path ) {
5503 if( path == null || path == undefined ) { return false; }
5504
5505 var pattern = /^((http|https|ftp|ftps|file):\/\/)/;
5506 if( !pattern.test(path) ) {
5507 // not a full URL
5508 return false;
5509 }
5510 return true;
5511 }
5512
5513 function GetContentApiObject() {
5514 var foundAlbumID=false;
5515 var nbTitles = 0;
5516 var AlbumPostProcess = NGY2Tools.AlbumPostProcess.bind(G);
5517
5518 G.I[0].contentIsLoaded=true;
5519
5520 jQuery.each(G.O.items, function(i,item){
5521
5522 var title = '';
5523 title=GetI18nItem(item, 'title');
5524 if( title === undefined ) { title=''; }
5525
5526 var src='';
5527 if( item['src'+RetrieveCurWidth().toUpperCase()] !== undefined ) {
5528 src = item['src'+RetrieveCurWidth().toUpperCase()];
5529 }
5530 else {
5531 src = item.src;
5532 }
5533 if( !StartsWithProtocol(src) ) {
5534 src = G.O.itemsBaseURL + src;
5535 }
5536
5537 var thumbsrc = '';
5538 if( item.srct !== undefined && item.srct.length > 0 ) {
5539 thumbsrc = item.srct;
5540 if( !StartsWithProtocol(thumbsrc) ) {
5541 thumbsrc = G.O.itemsBaseURL + thumbsrc;
5542 }
5543 }
5544 else {
5545 thumbsrc = src;
5546 }
5547
5548 var thumbsrcX2 = '';
5549 if( item.srct2x !== undefined && item.srct2x.length > 0 ) {
5550 thumbsrcX2 = item.srct2x;
5551 if( !StartsWithProtocol(thumbsrcX2) ) {
5552 thumbsrcX2 = G.O.itemsBaseURL + thumbsrcX2;
5553 }
5554 }
5555 else {
5556 if( thumbsrc != '' ) {
5557 thumbsrcX2 = thumbsrc;
5558 }
5559 else {
5560 thumbsrcX2 = src;
5561 }
5562 }
5563
5564 if( G.O.thumbnailLabel.get('title') != '' ) {
5565 title = GetImageTitle(src);
5566 }
5567
5568 var description=''; //'&nbsp;';
5569 description=GetI18nItem(item,'description');
5570 if( description === undefined ) { description=''; }
5571 //if( toType(item.description) == 'string' ) {
5572 // description=item.description;
5573 //}
5574
5575 var tags = GetI18nItem(item, 'tags');
5576 if( tags === undefined ) { tags=''; }
5577
5578 var albumID = 0;
5579 if( item.albumID !== undefined ) {
5580 albumID=item.albumID;
5581 foundAlbumID = true;
5582 }
5583 var ID = null;
5584 if( item.ID !== undefined ) {
5585 ID = item.ID;
5586 }
5587 var kind = 'image';
5588 if( item.kind !== undefined && item.kind.length > 0 ) {
5589 kind = item.kind;
5590 }
5591
5592 var newItem=NGY2Item.New( G, title, description, ID, albumID, kind, tags );
5593 if( title != '' ) {
5594 nbTitles++;
5595 }
5596
5597 // media source url - img is the default media kind
5598 newItem.setMediaURL( src, 'img');
5599
5600 // manage media kinds other than IMG
5601 jQuery.each(mediaList, function ( n, media ) {
5602 var id = media.getID(src);
5603 if( id != null ) {
5604 src = media.url(id);
5605 if( typeof media.thumbUrl == 'function' ) { thumbsrc = media.thumbUrl(id); }
5606 newItem.mediaKind = media.kind;
5607 newItem.mediaMarkup = media.markup(id);
5608 return false;
5609 }
5610 });
5611
5612 // image size
5613 if( item.imageWidth !== undefined ) { newItem.imageWidth = item.width; }
5614 if( item.imageHeight !== undefined ) { newItem.imageHeight = item.height; }
5615
5616 // THUMBNAILS
5617
5618 // thumbnail image size
5619 var tw = item.imgtWidth !== undefined ? item.imgtWidth : 0;
5620 var th = item.imgtHeight !== undefined ? item.imgtHeight : 0;
5621
5622 // default thumbnail URL and size
5623 newItem.thumbs = {
5624 url: { l1 : { xs: thumbsrc, sm: thumbsrc, me: thumbsrc, la: thumbsrc, xl: thumbsrc }, lN : { xs: thumbsrc, sm: thumbsrc, me: thumbsrc, la: thumbsrc, xl: thumbsrc } },
5625 width: { l1 : { xs: tw, sm: tw, me: tw, la: tw, xl: tw }, lN : { xs: tw, sm: tw, me: tw, la: tw, xl: tw } },
5626 height: { l1 : { xs: th, sm: th, me: th, la: th, xl: th }, lN : { xs: th, sm: th, me: th, la: th, xl: th } }
5627 };
5628
5629 // default media type -> IMG
5630 if( newItem.mediaKind == 'img' ) {
5631
5632 // responsive thumbnails URL and size
5633 var lst=['xs', 'sm', 'me', 'la', 'xl'];
5634 for( var i=0; i< lst.length; i++ ) {
5635 // url
5636 var turl = item['srct' + lst[i].toUpperCase()];
5637 if( turl !== undefined ) {
5638 if( !StartsWithProtocol(turl) ) {
5639 turl = G.O.itemsBaseURL + turl;
5640 }
5641 newItem.url.l1[lst[i]] = turl;
5642 newItem.url.lN[lst[i]] = turl;
5643 }
5644 // width
5645 var tw = item['imgt' + lst[i].toUpperCase() + 'Width'];
5646 if( tw != undefined ) {
5647 newItem.width.l1[lst[i]] = parseInt(tw);
5648 newItem.width.lN[lst[i]] = parseInt(tw);
5649 }
5650 // height
5651 var th = item['imgt' + lst[i].toUpperCase() + 'Height'];
5652 if( th != undefined ) {
5653 newItem.height.l1[lst[i]] = parseInt(th);
5654 newItem.height.lN[lst[i]] = parseInt(th);
5655 }
5656 }
5657 }
5658
5659 // dominant colors (needs to be a base64 gif)
5660 if( item.imageDominantColors !== undefined ) {
5661 newItem.imageDominantColors=item.imageDominantColors;
5662 }
5663 // dominant color (rgb hex)
5664 if( item.imageDominantColor !== undefined ) {
5665 newItem.imageDominantColor=item.imageDominantColor;
5666 }
5667
5668 // dest url
5669 if( item.destURL !== undefined && item.destURL.length>0 ) {
5670 newItem.destinationURL=item.destURL;
5671 }
5672
5673 // download image url
5674 if( item.downloadURL !== undefined && item.downloadURL.length>0 ) {
5675 newItem.downloadURL=item.downloadURL;
5676 }
5677
5678 // EXIF DATA
5679 // Exif - model
5680 if( item.exifModel !== undefined ) { newItem.exif.model = item.exifModel; }
5681 // Exif - flash
5682 if( item.exifFlash !== undefined ) { newItem.exif.flash = item.exifFlash; }
5683 // Exif - focallength
5684 if( item.exifFocalLength !== undefined ) { newItem.exif.focallength = item.exifFocalLength; }
5685 // Exif - fstop
5686 if( item.exifFStop !== undefined ) { newItem.exif.fstop = item.exifFStop; }
5687 // Exif - exposure
5688 if( item.exifExposure !== undefined ) { newItem.exif.exposure = item.exifExposure; }
5689 // Exif - time
5690 if( item.exifIso !== undefined ) { newItem.exif.iso = item.exifIso; }
5691 // Exif - iso
5692 if( item.exifTime !== undefined ) { newItem.exif.time = item.exifTime; }
5693 // Exif - location
5694 if( item.exifLocation !== undefined ) { newItem.exif.exifLocation = item.exifTime; }
5695
5696
5697 // custom data
5698 if( item.customData !== null ) {
5699 newItem.customData = cloneJSObject( item.customData );
5700 }
5701
5702 newItem.contentIsLoaded = true;
5703
5704 var fu = G.O.fnProcessData;
5705 if( fu !== null ) {
5706 typeof fu == 'function' ? fu(newItem, 'api', item) : window[fu](newItem, 'api', item);
5707 }
5708
5709 AlbumPostProcess(albumID);
5710 });
5711
5712 // if( foundAlbumID ) { G.O.displayBreadcrumb=true; }
5713 if( nbTitles == 0 ) { G.O.thumbnailLabel.display=false; }
5714
5715 }
5716
5717
5718 function GetContentMarkup( $elements ) {
5719 var foundAlbumID = false;
5720 var nbTitles = 0;
5721 var AlbumPostProcess = NGY2Tools.AlbumPostProcess.bind(G);
5722
5723 G.I[0].contentIsLoaded = true;
5724
5725 jQuery.each($elements, function(i, item){
5726
5727 // create dictionnary with all data attribute name in lowercase (to be case unsensitive)
5728 var data = {
5729 // some default values
5730 'data-ngdesc': '', // item description
5731 'data-ngid': null, // ID
5732 'data-ngkind': 'image', // kind (image, album, albumup)
5733 'data-ngtags': null, // tags
5734 'data-ngdest': '', // destination URL
5735 'data-ngthumbimgwidth': 0, // thumbnail width
5736 'data-ngthumbimgheight': 0, // thumbnail height
5737 'data-ngimagewidth': 0, // image width
5738 'data-ngimageheight': 0, // image height
5739 'data-ngimagedominantcolors': null, // image dominant colors
5740 'data-ngimagedominantcolor': null, // image dominant colors
5741 'data-ngexifmodel': '', // EXIF data
5742 'data-ngexifflash': '',
5743 'data-ngexiffocallength': '',
5744 'data-ngexiffstop': '',
5745 'data-ngexifexposure': '',
5746 'data-ngexifiso': '',
5747 'data-ngexiftime': '',
5748 'data-ngexiflocation': ''
5749 };
5750 [].forEach.call( item.attributes, function(attr) {
5751 data[attr.name.toLowerCase()] = attr.value;
5752 });
5753
5754 // responsive image source
5755 var src = '',
5756 st = RetrieveCurWidth().toUpperCase();
5757 if( data.hasOwnProperty('data-ngsrc'+st) ) {
5758 src = data['data-ngsrc'+st];
5759 }
5760 if( src == '' ) {
5761 src = data['href'];
5762 }
5763 if( !StartsWithProtocol(src) ) {
5764 src = G.O.itemsBaseURL + src;
5765 }
5766
5767 // thumbnail
5768 var thumbsrc = '';
5769 if( data.hasOwnProperty('data-ngthumb') ) {
5770 thumbsrc = data['data-ngthumb'];
5771 if( !StartsWithProtocol(thumbsrc) ) {
5772 thumbsrc = G.O.itemsBaseURL + thumbsrc;
5773 }
5774 }
5775 else {
5776 thumbsrc = src;
5777 }
5778 var thumbsrcX2 = '';
5779 if( data.hasOwnProperty('data-ngthumb2x') ) {
5780 thumbsrcX2 = data['data-ngthumb2x'];
5781 if( !StartsWithProtocol(thumbsrcX2) ) {
5782 thumbsrcX2 = G.O.itemsBaseURL + thumbsrcX2;
5783 }
5784 }
5785
5786 //newObj.description=jQuery(item).attr('data-ngdesc');
5787 var description = data['data-ngdesc'];
5788 var ID = data['id'];
5789 if( ID == undefined ) {
5790 ID = data['data-ngid'];
5791 }
5792 var kind = data['data-ngkind'];
5793 var tags = data['data-ngtags'];
5794
5795 var albumID = '0';
5796 if( data.hasOwnProperty('data-ngalbumid') ) {
5797 albumID = data['data-ngalbumid'];
5798 foundAlbumID = true;
5799 }
5800
5801 var title = jQuery(item).text();
5802 if( !(G.O.thumbnailLabel.get('title') == '' || G.O.thumbnailLabel.get('title') == undefined) ) {
5803 title = GetImageTitle(src);
5804 }
5805
5806
5807 var newItem = NGY2Item.New( G, title, description, ID, albumID, kind, tags );
5808 if( title != '' ) {
5809 nbTitles++;
5810 }
5811
5812 // media source url - img is the default media kind
5813 newItem.setMediaURL( src, 'img');
5814
5815 // manage media kinds other than IMG
5816 newItem.mediaKind = 'img';
5817 jQuery.each(mediaList, function ( n, media ) {
5818 var id = media.getID(src);
5819 if( id != null ) {
5820 src = media.url(id);
5821 if( typeof media.thumbUrl == 'function' ) { thumbsrc = media.thumbUrl(id); }
5822 newItem.mediaKind = media.kind;
5823 newItem.mediaMarkup = media.markup(id);
5824 return false;
5825 }
5826 });
5827
5828
5829 // image size
5830 newItem.imageWidth = parseInt( data['data-ngimagewidth'] );
5831 newItem.imageHeight = parseInt( data['data-ngimageheight'] );
5832
5833 // default thumbnail image URL and size
5834 var tw = parseInt(data['data-ngthumbimgwidth']);
5835 var th = parseInt(data['data-ngthumbimgheight']);
5836 newItem.thumbs = {
5837 url: { l1 : { xs: thumbsrc, sm: thumbsrc, me: thumbsrc, la: thumbsrc, xl: thumbsrc }, lN : { xs: thumbsrc, sm: thumbsrc, me: thumbsrc, la: thumbsrc, xl: thumbsrc } },
5838 width: { l1 : { xs: tw, sm: tw, me: tw, la: tw, xl: tw }, lN : { xs: tw, sm: tw, me: tw, la: tw, xl: tw } },
5839 height: { l1 : { xs: th, sm: th, me: th, la: th, xl: th }, lN : { xs: th, sm: th, me: th, la: th, xl: th } }
5840 };
5841
5842 // default media type -> IMG
5843 if( newItem.mediaKind == 'img' ) {
5844
5845 // responsive thumbnails URL and size
5846 var lst = ['xs', 'sm', 'me', 'la', 'xl'];
5847 for( var i = 0; i < lst.length; i++ ) {
5848 // url
5849 if( data.hasOwnProperty('data-ngthumb' + lst[i]) ) {
5850 var turl=data['data-ngthumb' + lst[i]];
5851 if( !StartsWithProtocol(turl) ) {
5852 turl = G.O.itemsBaseURL + turl;
5853 }
5854 newItem.url.l1[lst[i]] = turl;
5855 newItem.url.lN[lst[i]] = turl;
5856 }
5857
5858 // width
5859 if( data.hasOwnProperty('data-ngthumb' + lst[i] + 'width') ) {
5860 var tw=parseInt(data['data-ngthumb' + lst[i] + 'width']);
5861 newItem.width.l1[lst[i]] = tw;
5862 newItem.width.lN[lst[i]] = tw;
5863 }
5864 // height
5865 if( data.hasOwnProperty('data-ngthumb' + lst[i] + 'height') ) {
5866 var th=parseInt('data-ngthumb' + lst[i] + 'height');
5867 newItem.height.l1[lst[i]] = th;
5868 newItem.height.lN[lst[i]] = th;
5869 }
5870 }
5871 }
5872
5873
5874 // dominant colorS (needs to be a base64 gif)
5875 newItem.imageDominantColors = data['data-ngimagedominantcolors'];
5876 // dominant color (rgb hex)
5877 newItem.imageDominantColor = data['data-ngimagedominantcolors'];
5878
5879 newItem.destinationURL = data['data-ngdest'];
5880 newItem.downloadURL = data['data-ngdownloadurl'];
5881
5882 // Exif - model
5883 newItem.exif.model=data['data-ngexifmodel'];
5884 // Exif - flash
5885 newItem.exif.flash=data['data-ngexifflash'];
5886 // Exif - focallength
5887 newItem.exif.focallength=data['data-ngexiffocallength'];
5888 // Exif - fstop
5889 newItem.exif.fstop=data['data-ngexiffstop'];
5890 // Exif - exposure
5891 newItem.exif.exposure=data['data-ngexifexposure'];
5892 // Exif - iso
5893 newItem.exif.iso=data['data-ngexifiso'];
5894 // Exif - time
5895 newItem.exif.time=data['data-ngexiftime'];
5896 // Exif - location
5897 newItem.exif.location=data['data-ngexiflocation'];
5898
5899 newItem.contentIsLoaded=true;
5900
5901 // custom data
5902 if( jQuery(item).data('customdata') !== undefined ) {
5903 newItem.customData=cloneJSObject(jQuery(item).data('customdata'));
5904 }
5905 // custom data
5906 if( jQuery(item).data('ngcustomdata') !== undefined ) {
5907 newItem.customData=cloneJSObject(jQuery(item).data('ngcustomdata'));
5908 }
5909
5910 var fu=G.O.fnProcessData;
5911 if( fu !== null ) {
5912 typeof fu == 'function' ? fu(newItem, 'markup', item) : window[fu](newItem, 'markup', item);
5913 }
5914
5915 AlbumPostProcess(albumID);
5916
5917 });
5918
5919 // if( foundAlbumID ) { G.O.displayBreadcrumb=true; }
5920 if( nbTitles == 0 ) { G.O.thumbnailLabel.display = false; }
5921
5922 }
5923
5924
5925 // ################################
5926 // ##### DEFINE VARIABLES #####
5927 // ################################
5928
5929
5930 /** @function DefineVariables */
5931 function DefineVariables() {
5932
5933 // change 'picasa' to 'google' for compatibility reason
5934 if( G.O.kind.toUpperCase() == 'PICASA' || G.O.kind.toUpperCase() == 'GOOGLE') {
5935 G.O.kind='google2';
5936 }
5937
5938 // management of screen width
5939 G.GOM.cache.viewport = getViewport();
5940 G.GOM.curWidth = RetrieveCurWidth();
5941
5942 // tumbnail toolbar
5943 jQuery.extend(true, G.tn.toolbar.image, G.O.thumbnailToolbarImage );
5944 jQuery.extend(true, G.tn.toolbar.album, G.O.thumbnailToolbarAlbum );
5945 var t = ['image', 'album'];
5946 var pos= ['topLeft', 'topRight', 'bottomLeft', 'bottomRight']
5947 for( var i=0; i < t.length ; i++ ) {
5948 for( var j=0; j < pos.length ; j++ ) {
5949 G.tn.toolbar[t[i]][pos[j]] = G.tn.toolbar[t[i]][pos[j]].toUpperCase();
5950 }
5951 }
5952
5953 // thumbnails label - level dependant settings
5954 G.O.thumbnailLabel.get = function( opt ) {
5955 if( G.GOM.curNavLevel == 'l1' && G.O.thumbnailL1Label !== undefined && G.O.thumbnailL1Label[opt] !== undefined ) {
5956 return G.O.thumbnailL1Label[opt];
5957 }
5958 else {
5959 return G.O.thumbnailLabel[opt];
5960 }
5961 };
5962 G.O.thumbnailLabel.set = function( opt, value ) {
5963 if( G.GOM.curNavLevel == 'l1' && G.O.thumbnailL1Label !== undefined && G.O.thumbnailL1Label[opt] !== undefined ) {
5964 G.O.thumbnailL1Label[opt]=value;
5965 }
5966 else {
5967 G.O.thumbnailLabel[opt]=value;
5968 }
5969 };
5970
5971 if( G.O.blackList != '' ) { G.blackList=G.O.blackList.toUpperCase().split('|'); }
5972 if( G.O.whiteList != '' ) { G.whiteList=G.O.whiteList.toUpperCase().split('|'); }
5973
5974 if( G.O.albumList2 !== undefined && G.O.albumList2 !== null && G.O.albumList2.constructor === Array ) {
5975 var l=G.O.albumList2.length;
5976 for(var i=0; i< l; i++ ) {
5977 if( G.O.albumList2[i].indexOf('&authkey') !== -1 || G.O.albumList2[i].indexOf('?authkey') !== -1 ) {
5978 // private Google Photos album
5979 G.albumListHidden.push(G.O.albumList2[i]);
5980 }
5981 else {
5982 G.albumList.push(G.O.albumList2[i]);
5983 }
5984 }
5985 // G.albumList=G.O.albumList.toUpperCase().split('|');
5986 }
5987 if( G.O.albumList2 !== undefined && typeof G.O.albumList2 == 'string' ) {
5988 if( G.O.albumList2.indexOf('&authkey') !== -1 ) {
5989 // private Google Photos album
5990 G.albumListHidden.push(G.O.albumList2);
5991 }
5992 else {
5993 G.albumList.push(G.O.albumList2);
5994 }
5995 }
5996 if( G.albumListHidden.length > 0 ) {
5997 G.O.locationHash = false; // disable hash location for hidden/privat albums --> combination is impossible
5998 }
5999
6000
6001 // thumbnail image crop
6002 G.tn.opt.lN.crop = G.O.thumbnailCrop;
6003 G.tn.opt.l1.crop = G.O.thumbnailL1Crop != null ? G.O.thumbnailL1Crop : G.O.thumbnailCrop;
6004
6005
6006 function ThumbnailOpt( lN, l1, opt) {
6007 G.tn.opt.lN[opt]=G.O[lN];
6008 G.tn.opt.l1[opt]=G.O[lN];
6009 if( toType(G.O[l1]) == 'number' ) {
6010 G.tn.opt.l1[opt]=G.O[l1];
6011 }
6012 }
6013 // thumbnail stacks
6014 ThumbnailOpt('thumbnailStacks', 'thumbnailL1Stacks', 'stacks');
6015 // thumbnail stacks translate X
6016 ThumbnailOpt('thumbnailStacksTranslateX', 'thumbnailL1StacksTranslateX', 'stacksTranslateX');
6017 // thumbnail stacks translate Y
6018 ThumbnailOpt('thumbnailStacksTranslateY', 'thumbnailL1StacksTranslateY', 'stacksTranslateY');
6019 // thumbnail stacks translate Z
6020 ThumbnailOpt('thumbnailStacksTranslateZ', 'thumbnailL1StacksTranslateZ', 'stacksTranslateZ');
6021 // thumbnail stacks rotate X
6022 ThumbnailOpt('thumbnailStacksRotateX', 'thumbnailL1StacksRotateX', 'stacksRotateX');
6023 // thumbnail stacks rotate Y
6024 ThumbnailOpt('thumbnailStacksRotateY', 'thumbnailL1StacksRotateY', 'stacksRotateY');
6025 // thumbnail stacks rotate Z
6026 ThumbnailOpt('thumbnailStacksRotateZ', 'thumbnailL1StacksRotateZ', 'stacksRotateZ');
6027 // thumbnail stacks scale
6028 ThumbnailOpt('thumbnailStacksScale', 'thumbnailL1StacksScale', 'stacksScale');
6029 // thumbnail gutter width
6030 ThumbnailOpt('thumbnailGutterWidth', 'thumbnailL1GutterWidth', 'gutterWidth');
6031 // thumbnail gutter height
6032 ThumbnailOpt('thumbnailGutterHeight', 'thumbnailL1GutterHeight', 'gutterHeight');
6033 // thumbnail grid base height (for cascading layout)
6034 ThumbnailOpt('thumbnailBaseGridHeight', 'thumbnailL1BaseGridHeight', 'baseGridHeight');
6035
6036 // gallery display mode
6037 G.galleryDisplayMode.lN = G.O.galleryDisplayMode.toUpperCase();
6038 G.galleryDisplayMode.l1 = G.O.galleryL1DisplayMode != null ? G.O.galleryL1DisplayMode.toUpperCase() : G.O.galleryDisplayMode.toUpperCase();
6039
6040 // gallery maximum number of lines of thumbnails
6041 G.galleryMaxRows.lN = G.O.galleryMaxRows;
6042 G.galleryMaxRows.l1 = toType(G.O.galleryL1MaxRows) == 'number' ? G.O.galleryL1MaxRows : G.O.galleryMaxRows;
6043
6044 // gallery last row full
6045 G.galleryLastRowFull.lN = G.O.galleryLastRowFull;
6046 G.galleryLastRowFull.l1 = G.O.galleryL1LastRowFull != null ? G.O.galleryL1LastRowFull : G.O.galleryLastRowFull;
6047
6048 // gallery sorting
6049 G.gallerySorting.lN = G.O.gallerySorting.toUpperCase();
6050 G.gallerySorting.l1 = G.O.galleryL1Sorting != null ? G.O.galleryL1Sorting.toUpperCase() : G.gallerySorting.lN;
6051
6052 // gallery display transition
6053 G.galleryDisplayTransition.lN = G.O.galleryDisplayTransition.toUpperCase();
6054 G.galleryDisplayTransition.l1 = G.O.galleryL1DisplayTransition != null ? G.O.galleryL1DisplayTransition.toUpperCase() : G.galleryDisplayTransition.lN;
6055
6056 // gallery display transition duration
6057 G.galleryDisplayTransitionDuration.lN = G.O.galleryDisplayTransitionDuration;
6058 G.galleryDisplayTransitionDuration.l1 = G.O.galleryL1DisplayTransitionDuration != null ? G.O.galleryL1DisplayTransitionDuration : G.galleryDisplayTransitionDuration.lN;
6059
6060 // gallery max items per album (not for inline/api defined items)
6061 G.galleryMaxItems.lN = G.O.galleryMaxItems;
6062 G.galleryMaxItems.l1 = toType(G.O.galleryL1MaxItems) == 'number' ? G.O.galleryL1MaxItems : G.O.galleryMaxItems;
6063
6064 // gallery filter tags
6065 G.galleryFilterTags.lN = G.O.galleryFilterTags;
6066 G.galleryFilterTags.l1 = G.O.galleryL1FilterTags != null ? G.O.galleryL1FilterTags : G.O.galleryFilterTags;
6067
6068 // gallery pagination
6069 G.O.galleryPaginationMode = G.O.galleryPaginationMode.toUpperCase();
6070
6071 if( toType(G.O.slideshowDelay) == 'number' && G.O.slideshowDelay >= 2000 ) {
6072 G.VOM.slideshowDelay = G.O.slideshowDelay;
6073 }
6074 else {
6075 NanoConsoleLog(G, 'Parameter "slideshowDelay" must be an integer >= 2000 ms.');
6076 }
6077
6078 // gallery display transition
6079 if( typeof G.O.thumbnailDisplayTransition == 'boolean' ) {
6080 if( G.O.thumbnailDisplayTransition === true ) {
6081 G.tn.opt.lN.displayTransition = 'FADEIN';
6082 G.tn.opt.l1.displayTransition = 'FADEIN';
6083 }
6084 else {
6085 G.tn.opt.lN.displayTransition = 'NONE';
6086 G.tn.opt.l1.displayTransition = 'NONE';
6087 }
6088 }
6089
6090 if( G.O.fnThumbnailDisplayEffect !== '' ) {
6091 G.tn.opt.lN.displayTransition = 'CUSTOM';
6092 G.tn.opt.l1.displayTransition = 'CUSTOM';
6093 }
6094 if( G.O.fnThumbnailL1DisplayEffect !== '' ) {
6095 G.tn.opt.l1.displayTransition = 'CUSTOM';
6096 }
6097
6098 // parse thumbnail display transition
6099 function thumbnailDisplayTransitionParse( cfg, level ) {
6100 if( typeof cfg == 'string' ) {
6101 var st=cfg.split('_');
6102 if( st.length == 1 ) {
6103 G.tn.opt[level]['displayTransition'] = cfg.toUpperCase();
6104 }
6105 if( st.length == 2 ) {
6106 G.tn.opt[level]['displayTransition'] = st[0].toUpperCase();
6107 G.tn.opt[level]['displayTransitionStartVal'] = Number(st[1]);
6108 }
6109 if( st.length == 3 ) {
6110 G.tn.opt[level]['displayTransition'] = st[0].toUpperCase();
6111 G.tn.opt[level]['displayTransitionStartVal'] = Number(st[1]);
6112 G.tn.opt[level]['displayTransitionEasing'] = st[2];
6113 }
6114 }
6115 }
6116 thumbnailDisplayTransitionParse( G.O.thumbnailDisplayTransition, 'lN');
6117 thumbnailDisplayTransitionParse( G.O.thumbnailDisplayTransition, 'l1');
6118 thumbnailDisplayTransitionParse( G.O.thumbnailL1DisplayTransition, 'l1');
6119
6120
6121 // thumbnail display transition duration
6122 ThumbnailOpt('thumbnailDisplayTransitionDuration', 'thumbnailL1DisplayTransitionDuration', 'displayTransitionDuration');
6123 // thumbnail display transition interval duration
6124 ThumbnailOpt('thumbnailDisplayInterval', 'thumbnailL1DisplayInterval', 'displayInterval');
6125
6126
6127 // resolution breakpoints --> convert old syntax to new one
6128 if( G.O.thumbnailSizeSM !== undefined ) { G.O.breakpointSizeSM = G.O.thumbnailSizeSM; }
6129 if( G.O.thumbnailSizeME !== undefined ) { G.O.breakpointSizeME = G.O.thumbnailSizeME; }
6130 if( G.O.thumbnailSizeLA !== undefined ) { G.O.breakpointSizeLA = G.O.thumbnailSizeLA; }
6131 if( G.O.thumbnailSizeXL !== undefined ) { G.O.breakpointSizeXL = G.O.thumbnailSizeXL; }
6132
6133 // THUMBNAIL BUILD INIT
6134 //level 1
6135 if( G.O.thumbnailL1BuildInit2 !== undefined ) {
6136 var t1 = G.O.thumbnailL1BuildInit2.split('|');
6137 for( var i = 0; i < t1.length; i++ ) {
6138 var o1 = t1[i].trim().split('_');
6139 if( o1.length == 3 ) {
6140 var i1 = NewTBuildInit();
6141 i1.element = ThumbnailOverEffectsGetCSSElement(o1[0], '');
6142 i1.property = o1[1];
6143 i1.value = o1[2];
6144 G.tn.buildInit.level1.push(i1);
6145 }
6146 }
6147 }
6148 //level N
6149 if( G.O.thumbnailBuildInit2 !== undefined ) {
6150 var t1 = G.O.thumbnailBuildInit2.split('|');
6151 for( var i = 0; i < t1.length; i++ ) {
6152 var o1 = t1[i].trim().split('_');
6153 if( o1.length == 3 ) {
6154 var i1 = NewTBuildInit();
6155 i1.element = ThumbnailOverEffectsGetCSSElement(o1[0], '');
6156 i1.property = o1[1];
6157 i1.value = o1[2];
6158 G.tn.buildInit.std.push(i1);
6159 }
6160 }
6161 }
6162
6163
6164 // THUMBNAIL HOVER EFFETCS
6165
6166 // thumbnails hover effects - Level1
6167 var tL1HE = G.O.thumbnailL1HoverEffect2;
6168 if( tL1HE !== undefined ) {
6169 switch( toType(tL1HE) ) {
6170 case 'string':
6171 var tmp = tL1HE.split('|');
6172 for(var i = 0; i < tmp.length; i++) {
6173 var oDef = NewTHoverEffect();
6174 oDef = ThumbnailHoverEffectExtract( tmp[i].trim(), oDef );
6175 if( oDef != null ) {
6176 G.tn.hoverEffects.level1.push(oDef);
6177 }
6178 }
6179 break;
6180 case 'object':
6181 var oDef = NewTHoverEffect();
6182 oDef = jQuery.extend(oDef,tL1HE);
6183 oDef = ThumbnailHoverEffectExtract( oDef.name, oDef );
6184 if( oDef != null ) {
6185 G.tn.hoverEffects.level1.push(oDef);
6186 }
6187 break;
6188 case 'array':
6189 for(var i = 0; i < tL1HE.length; i++) {
6190 var oDef = NewTHoverEffect();
6191 oDef = jQuery.extend(oDef,tL1HE[i]);
6192 oDef = ThumbnailHoverEffectExtract( oDef.name, oDef );
6193 if( oDef != null ) {
6194 G.tn.hoverEffects.level1.push(oDef);
6195 }
6196 }
6197 break;
6198 case 'null':
6199 break;
6200 default:
6201 NanoAlert(G, 'incorrect parameter for "thumbnailL1HoverEffect2".');
6202 }
6203 }
6204 G.tn.hoverEffects.level1 = ThumbnailOverEffectsPreset(G.tn.hoverEffects.level1);
6205
6206 // thumbnails hover effects - other levels
6207 var tHE = G.O.thumbnailHoverEffect2;
6208 switch( toType(tHE) ) {
6209 case 'string':
6210 var tmp = tHE.split('|');
6211 for(var i = 0; i < tmp.length; i++) {
6212 var oDef = NewTHoverEffect();
6213 oDef = ThumbnailHoverEffectExtract( tmp[i].trim(), oDef );
6214 if( oDef != null ) {
6215 G.tn.hoverEffects.std.push(oDef);
6216 }
6217 }
6218 break;
6219 case 'object':
6220 var oDef = NewTHoverEffect();
6221 oDef = jQuery.extend(oDef, tHE);
6222 oDef = ThumbnailHoverEffectExtract( oDef.name, oDef );
6223 if( oDef != null ) {
6224 G.tn.hoverEffects.std.push(oDef);
6225 }
6226 break;
6227 case 'array':
6228 for(var i = 0; i < tHE.length; i++) {
6229 var oDef = NewTHoverEffect();
6230 oDef = jQuery.extend(oDef,tHE[i]);
6231 oDef = ThumbnailHoverEffectExtract( oDef.name, oDef );
6232 if( oDef!= null ) {
6233 G.tn.hoverEffects.std.push(oDef);
6234 }
6235 }
6236 break;
6237 case 'null':
6238 break;
6239 default:
6240 NanoAlert(G, 'incorrect parameter for "thumbnailHoverEffect2".');
6241 }
6242 G.tn.hoverEffects.std = ThumbnailOverEffectsPreset(G.tn.hoverEffects.std);
6243
6244 if( G.tn.hoverEffects.std.length == 0 ) {
6245 if( G.tn.hoverEffects.level1.length == 0 ) {
6246 G.O.touchAnimationL1 = false;
6247 }
6248 G.O.touchAnimation = false;
6249 }
6250
6251
6252 // thumbnail sizes
6253 if( G.O.thumbnailHeight == 0 || G.O.thumbnailHeight == '' ) { G.O.thumbnailHeight = 'auto'; }
6254 if( G.O.thumbnailWidth == 0 || G.O.thumbnailWidth == '' ) { G.O.thumbnailWidth = 'auto'; }
6255 if( G.O.thumbnailL1Height == 0 || G.O.thumbnailL1Height == '' ) { G.O.thumbnailL1Height = 'auto'; }
6256 if( G.O.thumbnailL1Width == 0 || G.O.thumbnailL1Width == '' ) { G.O.thumbnailL1Width = 'auto'; }
6257
6258 // RETRIEVE ALL THUMBNAIL SIZES
6259 function ThumbnailSizes( srcOpt, onlyl1, opt) {
6260 if( G.O[srcOpt] == null ) { return; }
6261
6262 if( toType(G.O[srcOpt]) == 'number' ) {
6263 ThumbnailsSetSize( opt, 'l1', G.O[srcOpt], 'u');
6264 if( !onlyl1 ) {
6265 ThumbnailsSetSize( opt, 'lN', G.O[srcOpt], 'u');
6266 }
6267 }
6268 else {
6269 var ws=G.O[srcOpt].split(' ');
6270 var v = 'auto';
6271 if( ws[0].substring(0,4) != 'auto' ) { v=parseInt(ws[0]); }
6272 var c = 'u';
6273 if( ws[0].charAt(ws[0].length - 1) == 'C' ) { c='c'; }
6274 ThumbnailsSetSize( opt, 'l1', v, c ); // default value for all resolutions and navigation levels
6275 if( !onlyl1 ) {
6276 ThumbnailsSetSize( opt, 'lN', v, c );
6277 }
6278 for( var i = 1; i < ws.length; i++ ) {
6279 var r = ws[i].substring(0,2).toLowerCase();
6280 if( /xs|sm|me|la|xl/i.test(r) ) {
6281 var w = ws[i].substring(2);
6282 var v = 'auto';
6283 if( w.substring(0,4) != 'auto' ) { v = parseInt(w); }
6284 var c = 'u';
6285 if( w.charAt(w.length - 1) == 'C' ) { c = 'c'; }
6286 G.tn.settings[opt]['l1'][r] = v;
6287 G.tn.settings[opt]['l1'][r + 'c'] = c;
6288 if( !onlyl1 ) {
6289 G.tn.settings[opt]['lN'][r] = v;
6290 G.tn.settings[opt]['lN'][r + 'c'] = c;
6291 }
6292 }
6293 }
6294 }
6295 }
6296 ThumbnailSizes( 'thumbnailWidth', false, 'width');
6297 ThumbnailSizes( 'thumbnailL1Width', true, 'width');
6298
6299 ThumbnailSizes( 'thumbnailHeight', false, 'height');
6300 ThumbnailSizes( 'thumbnailL1Height', true, 'height');
6301
6302
6303 G.O.thumbnailBorderHorizontal = parseInt(G.O.thumbnailBorderHorizontal);
6304 G.O.thumbnailBorderVertical = parseInt(G.O.thumbnailBorderVertical);
6305 G.O.thumbnailLabelHeight = parseInt(G.O.thumbnailLabelHeight);
6306
6307
6308 // retrieve all mosaic layout patterns
6309 // default pattern
6310 if( G.O.galleryMosaic != undefined ) {
6311 // clone object
6312 G.tn.settings.mosaic.l1.xs = JSON.parse(JSON.stringify(G.O.galleryMosaic));
6313 G.tn.settings.mosaic.l1.sm = JSON.parse(JSON.stringify(G.O.galleryMosaic));
6314 G.tn.settings.mosaic.l1.me = JSON.parse(JSON.stringify(G.O.galleryMosaic));
6315 G.tn.settings.mosaic.l1.la = JSON.parse(JSON.stringify(G.O.galleryMosaic));
6316 G.tn.settings.mosaic.l1.xl = JSON.parse(JSON.stringify(G.O.galleryMosaic));
6317 G.tn.settings.mosaic.lN.xs = JSON.parse(JSON.stringify(G.O.galleryMosaic));
6318 G.tn.settings.mosaic.lN.sm = JSON.parse(JSON.stringify(G.O.galleryMosaic));
6319 G.tn.settings.mosaic.lN.me = JSON.parse(JSON.stringify(G.O.galleryMosaic));
6320 G.tn.settings.mosaic.lN.la = JSON.parse(JSON.stringify(G.O.galleryMosaic));
6321 G.tn.settings.mosaic.lN.xl = JSON.parse(JSON.stringify(G.O.galleryMosaic));
6322 G.tn.settings.mosaicCalcFactor('l1', 'xs');
6323 G.tn.settings.mosaicCalcFactor('l1', 'sm');
6324 G.tn.settings.mosaicCalcFactor('l1', 'me');
6325 G.tn.settings.mosaicCalcFactor('l1', 'la');
6326 G.tn.settings.mosaicCalcFactor('l1', 'xl');
6327 G.tn.settings.mosaicCalcFactor('lN', 'xs');
6328 G.tn.settings.mosaicCalcFactor('lN', 'sm');
6329 G.tn.settings.mosaicCalcFactor('lN', 'me');
6330 G.tn.settings.mosaicCalcFactor('lN', 'la');
6331 G.tn.settings.mosaicCalcFactor('lN', 'xl');
6332 }
6333 if( G.O.galleryMosaicL1 != undefined ) {
6334 // default L1 pattern
6335 G.tn.settings.mosaic.l1.xs = JSON.parse(JSON.stringify(G.O.galleryMosaicL1));
6336 G.tn.settings.mosaic.l1.sm = JSON.parse(JSON.stringify(G.O.galleryMosaicL1));
6337 G.tn.settings.mosaic.l1.me = JSON.parse(JSON.stringify(G.O.galleryMosaicL1));
6338 G.tn.settings.mosaic.l1.la = JSON.parse(JSON.stringify(G.O.galleryMosaicL1));
6339 G.tn.settings.mosaic.l1.xl = JSON.parse(JSON.stringify(G.O.galleryMosaicL1));
6340 G.tn.settings.mosaicCalcFactor('l1', 'xs');
6341 G.tn.settings.mosaicCalcFactor('l1', 'sm');
6342 G.tn.settings.mosaicCalcFactor('l1', 'me');
6343 G.tn.settings.mosaicCalcFactor('l1', 'la');
6344 G.tn.settings.mosaicCalcFactor('l1', 'xl');
6345 }
6346 for( var w = 0; w < G.tn.settings.mosaic.l1; w++ ) {
6347 if( G.O['galleryMosaic' + G.tn.settings.mosaic.l1[w].toUpperCase()] != undefined ) {
6348 G.tn.settings.mosaic.lN[tn.settings.mosaic.l1[w]] = JSON.parse(JSON.stringify( G.O['galleryMosaic' + G.tn.settings.mosaic.l1[w].toUpperCase()] ));
6349 G.tn.settings.mosaic.l1[tn.settings.mosaic.l1[w]] = JSON.parse(JSON.stringify( G.O['galleryMosaic' + G.tn.settings.mosaic.l1[w].toUpperCase()] ));
6350 G.tn.settings.mosaicCalcFactor('l1', G.tn.settings.mosaic.l1[w]);
6351 G.tn.settings.mosaicCalcFactor('lN', G.tn.settings.mosaic.l1[w]);
6352 }
6353 }
6354 for( var w = 0; w < G.tn.settings.mosaic.l1; w++ ) {
6355 if( G.O['galleryMosaicL1'+G.tn.settings.mosaic.l1[w].toUpperCase()] != undefined ) {
6356 G.tn.settings.mosaic.l1[tn.settings.mosaic.l1[w]] = JSON.parse(JSON.stringify( G.O['galleryMosaicL1'+G.tn.settings.mosaic.l1[w].toUpperCase()] ));
6357 G.tn.settings.mosaicCalcFactor('l1', G.tn.settings.mosaic.l1[w]);
6358 }
6359 }
6360
6361 G.layout.SetEngine();
6362
6363 // init plugins
6364 switch( G.O.kind ) {
6365 // MARKUP / API
6366 case '':
6367 break;
6368 // JSON, Flickr, Picasa, ...
6369 default:
6370 jQuery.nanogallery2['data_' + G.O.kind](G, 'Init' );
6371 }
6372
6373 }
6374
6375 // HOVER EFFECTS
6376 function ThumbnailHoverEffectExtract( name, effect) {
6377 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'];
6378
6379 var sp = name.split('_');
6380 if( sp.length >= 4 ) {
6381 // var oDef=NewTHoverEffect();
6382 effect.name = '';
6383 effect.type = sp[1];
6384 effect.from = sp[2];
6385 effect.to = sp[3];
6386 if( sp.length >= 5 ) {
6387 // effect.duration=sp[4];
6388
6389 for( var n = 4; n < sp.length; n++ ) {
6390 var v = sp[n];
6391
6392 // check if an easing name
6393 var foundEasing = false;
6394 for( var e = 0; e < easings.length; e++) {
6395 if( v == easings[e] ) {
6396 foundEasing = true;
6397 effect.easing = v;
6398 break;
6399 }
6400 }
6401 if( foundEasing === true ) {
6402 continue;
6403 }
6404
6405 v = v.toUpperCase();
6406
6407 if( v == 'HOVERIN' ) {
6408 effect.hoverout = false;
6409 continue;
6410 }
6411 if( v == 'HOVEROUT' ) {
6412 effect.hoverin = false;
6413 continue;
6414 }
6415
6416 if( v == 'KEYFRAME' ) {
6417 effect.firstKeyframe = false;
6418 continue;
6419 }
6420
6421 var num = parseInt(v.replace(/[^0-9\.]/g, ''), 10); // extract a number if one exists
6422
6423 if( num > 0 ) {
6424 // the string contains a numbers > 0
6425 if( v.indexOf('DURATION') >= 0 ) {
6426 effect.duration = num;
6427 continue;
6428 }
6429 if( v.indexOf('DURATIONBACK') >= 0 ) {
6430 effect.durationBack = num;
6431 continue;
6432 }
6433 if( v.indexOf('DELAY') >= 0 ) {
6434 effect.delay = num;
6435 continue;
6436 }
6437 if( v.indexOf('DELAYBACK') >= 0 ) {
6438 effect.delayBack = num;
6439 continue;
6440 }
6441
6442 // no parameter name found -> default is duration
6443 effect.duration = num;
6444 }
6445 }
6446 }
6447 effect.element=ThumbnailOverEffectsGetCSSElement(sp[0], effect.type);
6448
6449 }
6450 else {
6451 effect.name = name;
6452 // NanoAlert(G, 'incorrect parameter for "thumbnailHoverEffect": ' + name);
6453 // return null;
6454 }
6455 return effect;
6456 }
6457
6458
6459 function ThumbnailOverEffectsGetCSSElement( element, property ) {
6460 var r = element;
6461
6462 switch ( element ) {
6463 case 'image':
6464 if( property == 'blur' || property == 'brightness' || property == 'grayscale' || property == 'sepia' || property == 'contrast' || property == 'opacity'|| property == 'saturate' ) {
6465 // r='.nGY2GThumbnailImg';
6466 r = '.nGY2GThumbnailImage';
6467 }
6468 else {
6469 r = '.nGY2GThumbnailImage';
6470 }
6471 break;
6472 case 'thumbnail':
6473 r = '.nGY2GThumbnail';
6474 break;
6475 case 'label':
6476 r = '.nGY2GThumbnailLabel';
6477 break;
6478 case 'title':
6479 r = '.nGY2GThumbnailTitle';
6480 break;
6481 case 'description':
6482 r = '.nGY2GThumbnailDescription';
6483 break;
6484 case 'tools':
6485 r = '.nGY2GThumbnailIcons';
6486 break;
6487 case 'customlayer':
6488 r = '.nGY2GThumbnailCustomLayer';
6489 break;
6490 }
6491 return r;
6492 }
6493
6494 // convert preset hover effects to new ones (nanogallery2)
6495 function ThumbnailOverEffectsPreset( effects ) {
6496
6497 // COMPATIBILITY WITH nanoGALLERY
6498 // OK:
6499 // 'borderLighter', 'borderDarker', 'scale120', 'labelAppear', 'labelAppear75', 'labelOpacity50', 'scaleLabelOverImage'
6500 // 'overScale', 'overScaleOutside', 'descriptionAppear'
6501 // 'slideUp', 'slideDown', 'slideRight', 'slideLeft'
6502 // 'imageScale150', 'imageScaleIn80', 'imageScale150Outside', 'imageSlideUp', 'imageSlideDown', 'imageSlideRight', 'imageSlideLeft'
6503 // 'labelSlideUpTop', 'labelSlideUp', 'labelSlideDown', 'descriptionSlideUp'
6504 // KO:
6505 // 'labelSplit4', 'labelSplitVert', 'labelAppearSplit4', 'labelAppearSplitVert'
6506 // TODO:
6507 // 'rotateCornerBL', 'rotateCornerBR', 'imageSplit4', 'imageSplitVert', 'imageRotateCornerBL', 'imageRotateCornerBR', 'imageFlipHorizontal', 'imageFlipVertical'
6508
6509
6510
6511 var newEffects=[];
6512 for( var i=0; i< effects.length; i++ ) {
6513 switch( effects[i].name.toUpperCase() ) {
6514 case 'BORDERLIGHTER':
6515 // var color=ngtinycolor(GalleryThemeGetCurrent().thumbnail.borderColor);
6516 // name='thumbnail_borderColor_'+color.toRgbString()+'_'+color.lighten(50).toRgbString();
6517
6518 var rgb = ColorHelperToRGB(GalleryThemeGetCurrent().thumbnail.borderColor);
6519 name = 'thumbnail_borderColor_'+rgb+'_'+ShadeBlendConvert(0.5, rgb );
6520 newEffects.push(ThumbnailHoverEffectExtract(name, effects[i]));
6521 break;
6522 case 'BORDERDARKER':
6523 // var color=ngtinycolor(GalleryThemeGetCurrent().thumbnail.borderColor);
6524 // name='thumbnail_borderColor_'+color.toRgbString()+'_'+color.darken(50).toRgbString();
6525 var rgb = ColorHelperToRGB(GalleryThemeGetCurrent().thumbnail.borderColor);
6526 name = 'thumbnail_borderColor_'+rgb+'_'+ShadeBlendConvert(-0.5, rgb );
6527 newEffects.push(ThumbnailHoverEffectExtract(name, effects[i]));
6528 break;
6529 case 'SCALE120':
6530 newEffects.push(ThumbnailHoverEffectExtract('thumbnail_scale_1.00_1.20', effects[i]));
6531 break;
6532 case 'LABELAPPEAR':
6533 case 'LABELAPPEAR75':
6534 newEffects.push(ThumbnailHoverEffectExtract('label_opacity_0.00_1.00', effects[i]));
6535 break;
6536 case 'TOOLSAPPEAR':
6537 newEffects.push(ThumbnailHoverEffectExtract('tools_opacity_0_1', effects[i]));
6538 break;
6539 case 'TOOLSSLIDEDOWN':
6540 newEffects.push(ThumbnailHoverEffectExtract('tools_translateY_-100%_0%', effects[i]));
6541 break;
6542 case 'TOOLSSLIDEUP':
6543 newEffects.push(ThumbnailHoverEffectExtract('tools_translateY_100%_0%', effects[i]));
6544 break;
6545 case 'LABELOPACITY50':
6546 newEffects.push(ThumbnailHoverEffectExtract('label_opacity_1.00_0.50', effects[i]));
6547 break;
6548 case 'SCALELABELOVERIMAGE':
6549 newEffects.push(ThumbnailHoverEffectExtract('label_scale_0.00_1.00', effects[i]));
6550 var n = cloneJSObject(effects[i]);
6551 newEffects.push(ThumbnailHoverEffectExtract('image_scale_1.00_0.00', n));
6552 break;
6553 case 'OVERSCALE':
6554 case 'OVERSCALEOUTSIDE':
6555 name = 'label_scale_0_100';
6556 newEffects.push(ThumbnailHoverEffectExtract('label_scale_2.00_1.00', effects[i]));
6557 var n = cloneJSObject(effects[i]);
6558 newEffects.push(ThumbnailHoverEffectExtract('label_opacity_0.00_1.00', n));
6559 n = cloneJSObject(effects[i]);
6560 newEffects.push(ThumbnailHoverEffectExtract('image_scale_1.00_0.00', n));
6561 n = cloneJSObject(effects[i]);
6562 newEffects.push(ThumbnailHoverEffectExtract('image_opacity_1.00_0.00', n));
6563 break;
6564 case 'DESCRIPTIONAPPEAR':
6565 newEffects.push(ThumbnailHoverEffectExtract('description_opacity_0_1', effects[i]));
6566 break;
6567 case 'SLIDERIGHT':
6568 newEffects.push(ThumbnailHoverEffectExtract('image_translateX_0%_100%', effects[i]));
6569 newEffects.push(ThumbnailHoverEffectExtract('label_translateX_-100%_0%', cloneJSObject(effects[i])));
6570 break;
6571 case 'SLIDELEFT':
6572 newEffects.push(ThumbnailHoverEffectExtract('image_translateX_0%_-100%', effects[i]));
6573 newEffects.push(ThumbnailHoverEffectExtract('label_translateX_100%_0%', cloneJSObject(effects[i])));
6574 break;
6575 case 'SLIDEUP':
6576 newEffects.push(ThumbnailHoverEffectExtract('image_translateY_0%_-100%', effects[i]));
6577 newEffects.push(ThumbnailHoverEffectExtract('label_translateY_100%_0%', cloneJSObject(effects[i])));
6578 break;
6579 case 'SLIDEDOWN':
6580 newEffects.push(ThumbnailHoverEffectExtract('image_translateY_0%_100%', effects[i]));
6581 newEffects.push(ThumbnailHoverEffectExtract('label_translateY_-100%_0%', cloneJSObject(effects[i])));
6582 break;
6583 case 'IMAGESCALE150':
6584 case 'IMAGESCALE150OUTSIDE':
6585 newEffects.push(ThumbnailHoverEffectExtract('image_scale_1.00_1.50', effects[i]));
6586 break;
6587 case 'IMAGESCALEIN80':
6588 newEffects.push(ThumbnailHoverEffectExtract('image_scale_1.20_1.00', effects[i]));
6589 break;
6590 case 'IMAGESLIDERIGHT':
6591 newEffects.push(ThumbnailHoverEffectExtract('image_translateX_0%_100%', effects[i]));
6592 break;
6593 case 'IMAGESLIDELEFT':
6594 newEffects.push(ThumbnailHoverEffectExtract('image_translateX_0%_-100%', effects[i]));
6595 break;
6596 case 'IMAGESLIDEUP':
6597 newEffects.push(ThumbnailHoverEffectExtract('image_translateY_0%_-100%', effects[i]));
6598 break;
6599 case 'IMAGESLIDEDOWN':
6600 newEffects.push(ThumbnailHoverEffectExtract('image_translateY_0%_100%', effects[i]));
6601 break;
6602 case 'LABELSLIDEUP':
6603 case 'LABELSLIDEUPTOP':
6604 newEffects.push(ThumbnailHoverEffectExtract('label_translateY_100%_0%', effects[i]));
6605 break;
6606 case 'LABELSLIDEUPDOWN':
6607 newEffects.push(ThumbnailHoverEffectExtract('label_translateY_0%_100%', effects[i]));
6608 break;
6609 case 'DESCRIPTIONSLIDEUP':
6610 newEffects.push(ThumbnailHoverEffectExtract('description_translateY_110%_0%', effects[i]));
6611 break;
6612
6613 case 'IMAGEBLURON':
6614 newEffects.push(ThumbnailHoverEffectExtract('image_blur_2.00px_0.00px', effects[i]));
6615 break;
6616 case 'IMAGEBLUROFF':
6617 newEffects.push(ThumbnailHoverEffectExtract('image_blur_0.00px_2.00px', effects[i]));
6618 break;
6619 case 'IMAGEGRAYON':
6620 newEffects.push(ThumbnailHoverEffectExtract('image_grayscale_0%_100%', effects[i]));
6621 break;
6622 case 'IMAGEGRAYOFF':
6623 newEffects.push(ThumbnailHoverEffectExtract('image_grayscale_100%_0%', effects[i]));
6624 break;
6625 case 'IMAGESEPIAON':
6626 newEffects.push(ThumbnailHoverEffectExtract('image_sepia_100%_1%', effects[i]));
6627 break;
6628 case 'IMAGESEPIAOFF':
6629 newEffects.push(ThumbnailHoverEffectExtract('image_sepia_1%_100%', effects[i]));
6630 break;
6631
6632 default:
6633 newEffects.push(effects[i]);
6634 break;
6635 }
6636 }
6637
6638 return newEffects;
6639 }
6640
6641
6642 // Thumbnail hover effect definition
6643 function NewTHoverEffect() {
6644 var oDef={
6645 name: '',
6646 element: '', // element class
6647 type: '',
6648 from: '', // start value
6649 to: '', // end value
6650 hoverin: true,
6651 hoverout: true,
6652 firstKeyframe: true,
6653 delay: 0,
6654 delayBack: 0,
6655 duration: 400,
6656 durationBack: 300,
6657 easing: 'easeOutQuart',
6658 easingBack: 'easeOutQuart',
6659 animParam: null
6660 };
6661 return oDef;
6662 }
6663
6664 function NewTBuildInit() {
6665 // to set CSS properties
6666 var oDef={ element: '', property: '', value: '' };
6667 return oDef;
6668 }
6669
6670
6671 function ThumbnailStyle( cfg, level) {
6672
6673 switch( cfg.position ){
6674 case 'onBottom' :
6675 G.tn.style[level]['label'] = 'bottom:0; ';
6676 break;
6677 case 'overImageOnTop' :
6678 G.tn.style[level]['label'] = 'top:0; position:absolute;';
6679 break;
6680 case 'overImageOnMiddle' :
6681 G.tn.style[level]['label'] = 'top:0; bottom:0;';
6682 G.tn.style[level]['title'] = 'position:absolute; bottom:50%;';
6683 G.tn.style[level]['desc'] = 'position:absolute; top:50%;';
6684 break;
6685 case 'custom' :
6686 break;
6687 case 'overImageOnBottom' :
6688 default :
6689 G.O.thumbnailLabel.position = 'overImageOnBottom';
6690 G.tn.style[level].label = 'bottom:0; position:absolute;';
6691 break;
6692 }
6693 // if( G.layout.engine != 'CASCADING' ) {
6694 if( cfg.position != 'onBottom' ) {
6695 // multi-line
6696 if( cfg.titleMultiLine ) {
6697 G.tn.style[level]['title'] = 'white-space:normal;';
6698 }
6699 if( cfg.descriptionMultiLine ) {
6700 G.tn.style[level]['desc'] = 'white-space:normal;';
6701 }
6702 }
6703
6704
6705 switch( cfg.align ) {
6706 case 'right':
6707 G.tn.style[level].label += 'text-align:right;';
6708 break;
6709 case 'left':
6710 G.tn.style[level].label += 'text-align:left;';
6711 break;
6712 default:
6713 G.tn.style[level].label += 'text-align:center;';
6714 break;
6715 }
6716 if( cfg.titleFontSize != undefined && cfg.titleFontSize != '' ) {
6717 G.tn.style[level].title += 'font-size:' + cfg.titleFontSize + ';';
6718 }
6719 if( cfg.descriptionFontSize != undefined && cfg.descriptionFontSize != '' ) {
6720 G.tn.style[level].desc += 'font-size:' + cfg.descriptionFontSize + ';';
6721 }
6722
6723 if( cfg.displayDescription == false ) {
6724 G.tn.style[level].desc += 'display:none;';
6725 }
6726 }
6727
6728
6729 // cache some thumbnail settings
6730 function ThumbnailDefCaches() {
6731 // thumbnail content CSS styles
6732
6733 // settings for level L1 and LN
6734 ThumbnailStyle( G.O.thumbnailLabel, 'lN');
6735 ThumbnailStyle( G.O.thumbnailLabel, 'l1');
6736
6737 if( G.O.thumbnailL1Label && G.O.thumbnailL1Label.display ) {
6738 // settings for level L1
6739 ThumbnailStyle( G.O.thumbnailL1Label, 'l1');
6740 }
6741
6742 G.tn.borderWidth = G.O.thumbnailBorderHorizontal;
6743 G.tn.borderHeight = G.O.thumbnailBorderVertical;
6744
6745
6746 // default thumbnail sizes levels l1 and lN
6747 var lst=['xs','sm','me','la','xl'];
6748 for( var i = 0; i < lst.length; i++ ) {
6749 var w = G.tn.settings.width.lN[lst[i]];
6750 if( w != 'auto' ) {
6751 G.tn.defaultSize.width.lN[lst[i]] = w;
6752 G.tn.defaultSize.width.l1[lst[i]] = w;
6753 }
6754 else {
6755 var h = G.tn.settings.height.lN[lst[i]];
6756 G.tn.defaultSize.width.lN[lst[i]] = h; // dynamic width --> set height value as default for the width
6757 G.tn.defaultSize.width.l1[lst[i]] = h; // dynamic width --> set height value as default
6758 }
6759 }
6760 for( var i = 0; i < lst.length; i++ ) {
6761 var h = G.tn.settings.height.lN[lst[i]];
6762 if( h != 'auto' ) {
6763 // grid or justified layout
6764 G.tn.defaultSize.height.lN[lst[i]] = h; //+G.tn.labelHeight.get();
6765 G.tn.defaultSize.height.l1[lst[i]] = h; //+G.tn.labelHeight.get();
6766 }
6767 else {
6768 var w = G.tn.settings.width.lN[lst[i]];
6769 G.tn.defaultSize.height.lN[lst[i]] = w; // dynamic height --> set width value as default for the height
6770 G.tn.defaultSize.height.l1[lst[i]] = w; // dynamic height --> set width value as default
6771 }
6772 }
6773
6774 // default thumbnail sizes levels l1
6775 for( var i = 0; i < lst.length; i++ ) {
6776 var w = G.tn.settings.width.l1[lst[i]];
6777 if( w != 'auto' ) {
6778 G.tn.defaultSize.width.l1[lst[i]] = w;
6779 }
6780 else {
6781 var h = G.tn.settings.height.l1[lst[i]];
6782 G.tn.defaultSize.width.l1[lst[i]] = h; // dynamic width --> set height value as default
6783 }
6784 }
6785 for( var i = 0; i < lst.length; i++ ) {
6786 var h = G.tn.settings.height.l1[lst[i]];
6787 if( h != 'auto' ) {
6788 // grid or justified layout
6789 G.tn.defaultSize.height.l1[lst[i]] = h; //+G.tn.labelHeight.get();
6790 }
6791 else {
6792 var w = G.tn.settings.width.l1[lst[i]];
6793 G.tn.defaultSize.height.l1[lst[i]]= w ; // dynamic height --> set width value as default
6794 }
6795 }
6796
6797 }
6798
6799
6800 // ##### THUMBNAIL SIZE MANAGEMENT
6801 function ThumbnailsSetSize( dir, level, v, crop ) {
6802 G.tn.settings[dir][level]['xs'] = v;
6803 G.tn.settings[dir][level]['sm'] = v;
6804 G.tn.settings[dir][level]['me'] = v;
6805 G.tn.settings[dir][level]['la'] = v;
6806 G.tn.settings[dir][level]['xl'] = v;
6807 G.tn.settings[dir][level]['xsc'] = crop;
6808 G.tn.settings[dir][level]['smc'] = crop;
6809 G.tn.settings[dir][level]['mec'] = crop;
6810 G.tn.settings[dir][level]['lac'] = crop;
6811 G.tn.settings[dir][level]['xlc'] = crop;
6812 }
6813
6814
6815 //
6816 function GalleryThemeGetCurrent() {
6817
6818 var cs=null;
6819 switch(toType(G.O.galleryTheme)) {
6820 case 'object': // user custom color scheme object
6821 cs = G.galleryTheme_dark; // default color scheme
6822 jQuery.extend(true,cs,G.O.galleryTheme);
6823 break;
6824 case 'string': // name of an internal defined color scheme
6825 switch( G.O.galleryTheme ) {
6826 case 'light':
6827 cs = G.galleryTheme_light;
6828 break;
6829 case 'default':
6830 case 'dark':
6831 case 'none':
6832 default:
6833 cs = G.galleryTheme_dark;
6834 }
6835 break;
6836 default:
6837 cs = G.galleryTheme_dark;
6838 }
6839 return cs;
6840 }
6841
6842 // ##### BREADCRUMB/THUMBNAIL COLOR SCHEME #####
6843 function SetGalleryTheme() {
6844
6845 if( typeof G.O.colorScheme !== 'undefined' ) {
6846 G.O.galleryTheme = G.O.colorScheme;
6847 }
6848
6849 var cs = null;
6850 var galleryTheme = '';
6851 switch(toType(G.O.galleryTheme)) {
6852 case 'object': // user custom color scheme object
6853 cs = G.galleryTheme_dark; // default color scheme
6854 jQuery.extend(true,cs,G.O.galleryTheme);
6855 galleryTheme='nanogallery_gallerytheme_custom_' + G.baseEltID;
6856 break;
6857 case 'string': // name of an internal defined color scheme
6858 switch( G.O.galleryTheme ) {
6859 case 'light':
6860 cs = G.galleryTheme_light;
6861 galleryTheme='nanogallery_gallerytheme_light_' + G.baseEltID;
6862 break;
6863 case 'default':
6864 case 'dark':
6865 case 'none':
6866 default:
6867 cs = G.galleryTheme_dark;
6868 galleryTheme='nanogallery_gallerytheme_dark_' + G.baseEltID;
6869 }
6870 break;
6871 default:
6872 NanoAlert(G, 'Error in galleryTheme parameter.');
6873 return;
6874 }
6875
6876 //var s1='.nanogallery_theme_'+G.O.theme+' ';
6877 var s1='.' + galleryTheme + ' ';
6878
6879 // navigation bar
6880 var s=s1+'.nGY2Navigationbar { background:'+cs.navigationBar.background+'; }'+'\n';
6881 if( cs.navigationBar.border !== undefined && cs.navigationBar.border !== '' ) { s+=s1+'.nGY2Navigationbar { border:'+cs.navigationBar.border+'; }'+'\n'; }
6882 if( cs.navigationBar.borderTop !== undefined && cs.navigationBar.borderTop !== '' ) { s+=s1+'.nGY2Navigationbar { border-top:'+cs.navigationBar.borderTop+'; }'+'\n'; }
6883 if( cs.navigationBar.borderBottom !== undefined && cs.navigationBar.borderBottom !== '' ) { s+=s1+'.nGY2Navigationbar { border-bottom:'+cs.navigationBar.borderBottom+'; }'+'\n'; }
6884 if( cs.navigationBar.borderRight !== undefined && cs.navigationBar.borderRight !== '' ) { s+=s1+'.nGY2Navigationbar { border-right:'+cs.navigationBar.borderRight+'; }'+'\n'; }
6885 if( cs.navigationBar.borderLeft !== undefined && cs.navigationBar.borderLeft !== '' ) { s+=s1+'.nGY2Navigationbar { border-left:'+cs.navigationBar.borderLeft+'; }'+'\n'; }
6886
6887 // navigation bar - breadcrumb
6888 s+=s1+'.nGY2Breadcrumb { background:'+cs.navigationBreadcrumb.background+'; border-radius:'+cs.navigationBreadcrumb.borderRadius+'; }'+'\n';
6889 s+=s1+'.nGY2Breadcrumb .oneItem { color:'+cs.navigationBreadcrumb.color+'; }'+'\n';
6890 s+=s1+'.nGY2Breadcrumb .oneItem:hover { color:'+cs.navigationBreadcrumb.colorHover+'; }'+'\n';
6891
6892 // navigation bar - tag filter
6893 s+=s1+'.nGY2NavFilterUnselected { color:'+cs.navigationFilter.color+'; background:'+cs.navigationFilter.background+'; border-radius:'+cs.navigationFilter.borderRadius+'; }'+'\n';
6894 s+=s1+'.nGY2NavFilterSelected { color:'+cs.navigationFilter.colorSelected+'; background:'+cs.navigationFilter.backgroundSelected+'; border-radius:'+cs.navigationFilter.borderRadius+'; }'+'\n';
6895 s+=s1+'.nGY2NavFilterSelectAll { color:'+cs.navigationFilter.colorSelected+'; background:'+cs.navigationFilter.background+'; border-radius:'+cs.navigationFilter.borderRadius+'; }'+'\n';
6896
6897 // thumbnails
6898 s+=s1+'.nGY2GThumbnail { background:'+cs.thumbnail.background+'; border-color:'+cs.thumbnail.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';
6899 s+=s1+'.nGY2GThumbnailStack { background:'+cs.thumbnail.stackBackground+'; }'+'\n';
6900 // s+=s1+'.nGY2GThumbnailImage { background:'+cs.thumbnail.background+'; background-image:'+cs.thumbnail.backgroundImage+'; }'+'\n';
6901 s+=s1+'.nGY2TnImgBack { background:'+cs.thumbnail.background+'; background-image:'+cs.thumbnail.backgroundImage+'; }'+'\n';
6902 s+=s1+'.nGY2GThumbnailAlbumUp { background:'+cs.thumbnail.background+'; background-image:'+cs.thumbnail.backgroundImage+'; color:'+cs.thumbnail.titleColor+'; }'+'\n';
6903 s+=s1+'.nGY2GThumbnailIconsFullThumbnail { color:'+cs.thumbnail.titleColor+'; }\n';
6904 s+=s1+'.nGY2GThumbnailLabel { background:'+cs.thumbnail.labelBackground+'; opacity:'+cs.thumbnail.labelOpacity+'; }'+'\n';
6905 s+=s1+'.nGY2GThumbnailImageTitle { color:'+cs.thumbnail.titleColor+'; background-color:'+cs.thumbnail.titleBgColor+'; '+(cs.thumbnail.titleShadow =='' ? '': 'Text-Shadow:'+cs.thumbnail.titleShadow+';')+' }'+'\n';
6906 s+=s1+'.nGY2GThumbnailAlbumTitle { color:'+cs.thumbnail.titleColor+'; background-color:'+cs.thumbnail.titleBgColor+'; '+(cs.thumbnail.titleShadow =='' ? '': 'Text-Shadow:'+cs.thumbnail.titleShadow+';')+' }'+'\n';
6907 s+=s1+'.nGY2GThumbnailDescription { color:'+cs.thumbnail.descriptionColor+'; background-color:'+cs.thumbnail.descriptionBgColor+'; '+(cs.thumbnail.descriptionShadow =='' ? '': 'Text-Shadow:'+cs.thumbnail.descriptionShadow+';')+' }'+'\n';
6908
6909 // thumbnails - icons
6910 s+=s1+'.nGY2GThumbnailIcons { padding:'+cs.thumbnailIcon.padding+'; }\n';
6911 s+=s1+'.nGY2GThumbnailIcon { color:'+cs.thumbnailIcon.color+'; }\n';
6912 s+=s1+'.nGY2GThumbnailIconTextBadge { background-color:'+cs.thumbnailIcon.color+'; }\n';
6913
6914 // gallery pagination -> dot/rectangle based
6915 if( G.O.galleryPaginationMode != 'NUMBERS' ) {
6916 s+=s1+'.nGY2paginationDot { border:'+cs.pagination.shapeBorder+'; background:'+cs.pagination.shapeColor+';}\n';
6917 s+=s1+'.nGY2paginationDotCurrentPage { border:'+cs.pagination.shapeBorder+'; background:'+cs.pagination.shapeSelectedColor+';}\n';
6918 s+=s1+'.nGY2paginationRectangle { border:'+cs.pagination.shapeBorder+'; background:'+cs.pagination.shapeColor+';}\n';
6919 s+=s1+'.nGY2paginationRectangleCurrentPage { border:'+cs.pagination.shapeBorder+'; background:'+cs.pagination.shapeSelectedColor+';}\n';
6920 } else {
6921 s+=s1+'.nGY2paginationItem { background:'+cs.pagination.background+'; color:'+cs.pagination.color+'; border-radius:'+cs.pagination.borderRadius+'; }\n';
6922 s+=s1+'.nGY2paginationItemCurrentPage { background:'+cs.pagination.background+'; color:'+cs.pagination.color+'; border-radius:'+cs.pagination.borderRadius+'; }\n';
6923 s+=s1+'.nGY2PaginationPrev { background:'+cs.pagination.background+'; color:'+cs.pagination.color+'; border-radius:'+cs.pagination.borderRadius+'; }\n';
6924 s+=s1+'.nGY2PaginationNext { background:'+cs.pagination.background+'; color:'+cs.pagination.color+'; border-radius:'+cs.pagination.borderRadius+'; }\n';
6925 s+=s1+'.nGY2paginationItemCurrentPage { background:'+cs.pagination.backgroundSelected+'; }\n';
6926 }
6927
6928 // gallery more button
6929 s+=s1+'.nGY2GalleryMoreButtonAnnotation { background:'+cs.thumbnail.background+'; border-color:'+cs.thumbnail.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';
6930 s+=s1+'.nGY2GalleryMoreButtonAnnotation { color:'+cs.thumbnail.titleColor+'; '+(cs.thumbnail.titleShadow =='' ? '': 'Text-Shadow:'+cs.thumbnail.titleShadow)+'; }\n';
6931
6932 jQuery('head').append('<style id="ngycs_'+G.baseEltID+'">'+s+'</style>');
6933 G.$E.base.addClass(galleryTheme);
6934
6935 };
6936
6937 // ##### VIEWER COLOR SCHEME #####
6938 function SetViewerTheme( ) {
6939
6940 if( G.VOM.viewerTheme != '' ) {
6941 G.VOM.$cont.addClass(G.VOM.viewerTheme);
6942 return;
6943 }
6944
6945 if( typeof G.O.colorSchemeViewer !== 'undefined' ) {
6946 G.VOM.viewerTheme = G.O.colorSchemeViewer;
6947 }
6948
6949 var cs=null;
6950 switch(toType(G.O.viewerTheme)) {
6951 case 'object': // user custom color scheme object
6952 cs = G.viewerTheme_dark;
6953 jQuery.extend(true,cs,G.O.viewerTheme);
6954 G.VOM.viewerTheme = 'nanogallery_viewertheme_custom_'+G.baseEltID;
6955 break;
6956 case 'string': // name of an internal defined color scheme
6957 switch( G.O.viewerTheme ) {
6958 case 'none':
6959 return;
6960 break;
6961 case 'light':
6962 cs = G.viewerTheme_light;
6963 G.VOM.viewerTheme = 'nanogallery_viewertheme_light_' + G.baseEltID;
6964 break;
6965 case 'border':
6966 cs = G.viewerTheme_border;
6967 G.VOM.viewerTheme = 'nanogallery_viewertheme_border_' + G.baseEltID;
6968 break;
6969 case 'dark':
6970 case 'default':
6971 cs = G.viewerTheme_dark;
6972 G.VOM.viewerTheme = 'nanogallery_viewertheme_dark_' + G.baseEltID;
6973 break;
6974 }
6975 break;
6976 default:
6977 NanoAlert(G, 'Error in viewerTheme parameter.');
6978 return;
6979 }
6980
6981 var s1 = '.' + G.VOM.viewerTheme + ' ';
6982 var s = s1+'.nGY2Viewer { background:'+cs.background+'; }'+'\n';
6983 s += s1+'.nGY2ViewerMedia { border:'+cs.imageBorder+'; box-shadow:'+cs.imageBoxShadow+'; }'+'\n';
6984 s += s1+'.nGY2Viewer .toolbarBackground { background:'+cs.barBackground+'; }'+'\n';
6985 s += s1+'.nGY2Viewer .toolbar { border:'+cs.barBorder+'; color:'+cs.barColor+'; }'+'\n';
6986 s += s1+'.nGY2Viewer .toolbar .previousButton:after { color:'+cs.barColor+'; }'+'\n';
6987 s += s1+'.nGY2Viewer .toolbar .nextButton:after { color:'+cs.barColor+'; }'+'\n';
6988 s += s1+'.nGY2Viewer .toolbar .closeButton:after { color:'+cs.barColor+'; }'+'\n';
6989 s += s1+'.nGY2Viewer .toolbar .label .title { color:'+cs.barColor+'; }'+'\n';
6990 s += s1+'.nGY2Viewer .toolbar .label .description { color:'+cs.barDescriptionColor+'; }'+'\n';
6991 jQuery('head').append('<style>' + s + '</style>');
6992 G.VOM.$cont.addClass(G.VOM.viewerTheme);
6993 };
6994
6995
6996
6997 /** @function SetPolyFills */
6998 function SetPolyFills() {
6999
7000 // POLYFILL FOR BIND function --> for older Safari mobile
7001 // found on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind#Compatibility
7002 if (!Function.prototype.bind) {
7003 Function.prototype.bind = function (oThis) {
7004 if (typeof this !== "function") {
7005 // closest thing possible to the ECMAScript 5
7006 // internal IsCallable function
7007 throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
7008 }
7009
7010 var aArgs = Array.prototype.slice.call(arguments, 1),
7011 fToBind = this,
7012 fNOP = function () {},
7013 fBound = function () {
7014 return fToBind.apply(this instanceof fNOP && oThis
7015 ? this
7016 : oThis,
7017 aArgs.concat(Array.prototype.slice.call(arguments)));
7018 };
7019
7020 fNOP.prototype = this.prototype;
7021 fBound.prototype = new fNOP();
7022
7023 return fBound;
7024 };
7025 }
7026
7027 // requestAnimationFrame polyfill by Erik M�ller. fixes from Paul Irish and Tino Zijdel
7028 // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
7029 // http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
7030 // MIT license
7031 (function() {
7032 var lastTime = 0;
7033 var vendors = ['ms', 'moz', 'webkit', 'o'];
7034 for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
7035 window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
7036 window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
7037 }
7038 if (!window.requestAnimationFrame)
7039 window.requestAnimationFrame = function(callback, element) {
7040 var currTime = new Date().getTime();
7041 var timeToCall = Math.max(0, 16 - (currTime - lastTime));
7042 var id = window.setTimeout(function() { callback(currTime + timeToCall); }, timeToCall);
7043 lastTime = currTime + timeToCall;
7044 return id;
7045 };
7046
7047 if (!window.cancelAnimationFrame)
7048 window.cancelAnimationFrame = function(id) {
7049 clearTimeout(id);
7050 };
7051 }());
7052
7053 // array.removeIf -> removes items from array base on a function's result
7054 Array.prototype.removeIf = function(callback) {
7055 var i = this.length;
7056 while (i--) {
7057 if (callback(this[i], i)) {
7058 this.splice(i, 1);
7059 }
7060 }
7061 };
7062
7063 }
7064
7065
7066 // Gallery clicked/touched -> retrieve & execute action
7067 function GalleryClicked(e) {
7068
7069 var r = GalleryEventRetrieveElementl(e, false);
7070
7071 if( r.GOMidx == -1 ) { return 'exit'; }
7072
7073 var idx = G.GOM.items[r.GOMidx].thumbnailIdx;
7074 if( G.GOM.slider.hostIdx == r.GOMidx ) {
7075 // slider on thumbnail -> open the displayed image
7076 idx = G.GOM.items[G.GOM.slider.currentIdx].thumbnailIdx;
7077 }
7078 switch( r.action ) {
7079 case 'OPEN':
7080 ThumbnailOpen(idx, false);
7081 return 'exit';
7082 break;
7083 case 'DISPLAY':
7084 // used the display icon (ignore if selection mode)
7085 ThumbnailOpen(idx, true);
7086 return 'exit';
7087 break;
7088 case 'TOGGLESELECT':
7089 ThumbnailSelectionToggle(idx);
7090 return 'exit';
7091 break;
7092 case 'SHARE':
7093 PopupShare(idx);
7094 return 'exit';
7095 break;
7096 case 'DOWNLOAD':
7097 DownloadImage(idx);
7098 return 'exit';
7099 break;
7100 case 'INFO':
7101 ItemDisplayInfo(G.I[idx]);
7102 return 'exit';
7103 break;
7104 case 'CART':
7105 AddToCart(idx);
7106 return 'exit';
7107 break;
7108 default:
7109 // all other actions (custom1..10, or anything else)
7110 var fu = G.O.fnThumbnailToolCustAction;
7111 if( fu !== null ) {
7112 typeof fu == 'function' ? fu(r.action, G.I[idx]) : window[fu](r.action, G.I[idx]);
7113 }
7114 break;
7115 }
7116 }
7117
7118 // Download an image
7119 function DownloadImage(idx) {
7120 if( G.I[idx].mediaKind != 'img' ) { return; }
7121
7122
7123 var url=G.I[idx].src;
7124
7125 if( G.I[idx].downloadURL != undefined && G.I[idx].downloadURL != '' ) {
7126 url=G.I[idx].downloadURL;
7127 }
7128
7129 var a = document.createElement('a');
7130 a.href = url;
7131 a.download = url.split('.').pop();
7132 document.body.appendChild(a);
7133 a.click();
7134 document.body.removeChild(a);
7135
7136 }
7137
7138 // add one image to the shopping cart
7139 function AddToCart( idx ) {
7140 // increment counter if already in shopping cart
7141 var found=false;
7142 for( var i=0; i<G.shoppingCart.length; i++ ) {
7143 if( G.shoppingCart[i].idx == idx ) {
7144 G.shoppingCart[i].cnt++;
7145 var fu=G.O.fnShoppingCartUpdated;
7146 if( fu !== null ) {
7147 typeof fu == 'function' ? fu(G.shoppingCart) : window[fu](G.shoppingCart);
7148 }
7149 TriggerCustomEvent('shoppingCartUpdated');
7150 return;
7151 }
7152 }
7153
7154 // add to shopping cart
7155 if( !found) {
7156 G.shoppingCart.push( { idx:idx, ID:G.I[idx].GetID(), cnt:1} );
7157 var fu=G.O.fnShoppingCartUpdated;
7158 if( fu !== null ) {
7159 typeof fu == 'function' ? fu(G.shoppingCart) : window[fu](G.shoppingCart);
7160 }
7161 TriggerCustomEvent('shoppingCartUpdated');
7162 }
7163 }
7164
7165
7166 // All thumbnails are set to unselected
7167 function ThumbnailSelectionClear() {
7168 G.GOM.nbSelected = 0;
7169 for( var i = 0, nbTn = G.GOM.items.length; i < nbTn ; i++ ) {
7170 var item = G .I[G.GOM.items[i].thumbnailIdx];
7171 if( item.selected ) {
7172 item.selected = false;
7173 var fu = G.O.fnThumbnailSelection;
7174 if( fu !== null ) {
7175 typeof fu == 'function' ? fu(item.$elt, item, G.I) : window[fu](item.$elt, item, G.I);
7176 }
7177 }
7178 item.selected = false;
7179 }
7180 }
7181
7182 function ThumbnailSelectionToggle( idx ){
7183 var item = G.I[idx];
7184 if( item.selected === true ) {
7185 ThumbnailSelectionSet(item, false);
7186 G.GOM.nbSelected--;
7187 TriggerCustomEvent('itemUnSelected');
7188 }
7189 else {
7190 ThumbnailSelectionSet(item, true);
7191 G.GOM.nbSelected++;
7192 TriggerCustomEvent('itemSelected');
7193 }
7194 }
7195
7196
7197 // this replaces ThumbnailSelection()
7198 function ThumbnailSelectionSet(item, selected ){
7199
7200 item.selected = selected;
7201
7202 ThumbnailSelectionSetIcon( item );
7203
7204 // called when the selection status of an item changed
7205 var fu=G.O.fnThumbnailSelection;
7206 if( fu !== null ) {
7207 typeof fu == 'function' ? fu(item.$elt, item, G.I) : window[fu](item.$elt, item, G.I);
7208 }
7209
7210 }
7211
7212 function ThumbnailSelectionSetIcon( item ) {
7213 if( item.$elt == null ) {
7214 // thumbnail is not built
7215 return;
7216 }
7217 // var $sub = item.$getElt('.nGY2GThumbnailSub');
7218 var $sub = item.$getElt('.nGY2GThumbnail');
7219 var $icon = item.$getElt('.nGY2GThumbnailIconImageSelect');
7220 if( item.selected === true) {
7221 $sub.addClass('nGY2GThumbnailSubSelected');
7222 $icon.addClass('nGY2ThumbnailSelected');
7223 $icon.removeClass('nGY2ThumbnailUnselected');
7224 $icon.html(G.O.icons.thumbnailSelected);
7225 }
7226 else {
7227 $sub.removeClass('nGY2GThumbnailSubSelected');
7228 $icon.removeClass('nGY2ThumbnailSelected');
7229 $icon.addClass('nGY2ThumbnailUnselected');
7230 $icon.html(G.O.icons.thumbnailUnselected);
7231 }
7232 }
7233
7234
7235 // display a modal popup for sharing image/album
7236 function PopupShare(idx) {
7237
7238 // SEE SAMPLES: https://gist.github.com/chrisjlee/5196139
7239 // https://github.com/Julienh/Sharrre
7240
7241 var item=G.I[idx];
7242
7243 var currentURL=document.location.protocol + '//' + document.location.hostname + document.location.pathname;
7244 var newLocationHash = '#nanogallery/' + G.baseEltID + '/';
7245 if( item.kind == 'image' ) {
7246 newLocationHash += item.albumID + '/' + item.GetID();
7247 }
7248 else {
7249 newLocationHash += item.GetID();
7250 }
7251
7252 var content = '';
7253 content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="facebook">' + G.O.icons.shareFacebook + '</div>';
7254 content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="pinterest">' + G.O.icons.sharePinterest + '</div>';
7255 content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="tumblr">' + G.O.icons.shareTumblr + '</div>';
7256 content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="twitter">' + G.O.icons.shareTwitter + '</div>';
7257 content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="googleplus">' + G.O.icons.shareGooglePlus + '</div>';
7258 content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="vk">' + G.O.icons.shareVK + '</div>';
7259 content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="mail">' + G.O.icons.shareMail + '</div>';
7260 content += '<div class="nGY2PopupOneItem" style="text-align:center;"></div>';
7261 content += '<input class="nGY2PopupOneItemText" readonly type="text" value="' + currentURL+newLocationHash + '" style="width:100%;text-align:center;">';
7262 content += '<br>';
7263
7264 currentURL = encodeURIComponent(document.location.protocol + '//' + document.location.hostname + document.location.pathname + newLocationHash);
7265
7266 var currentTitle = item.title;
7267 var currentTn = item.thumbImg().src;
7268
7269
7270 Popup('Share to:', content, 'Center');
7271
7272 G.popup.$elt.find('.nGY2PopupOneItem').on('click', function(e) {
7273 e.stopPropagation();
7274
7275 var shareURL = '';
7276 var found = true;
7277 switch(jQuery(this).attr('data-share').toUpperCase()) {
7278 case 'FACEBOOK':
7279 // <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>
7280 //window.open("https://www.facebook.com/sharer.php?u="+currentURL,"","height=368,width=600,left=100,top=100,menubar=0");
7281 shareURL = 'https://www.facebook.com/sharer.php?u=' + currentURL;
7282 break;
7283 case 'VK':
7284 shareURL = 'http://vk.com/share.php?url=' + currentURL;
7285 break;
7286 case 'GOOGLEPLUS':
7287 shareURL = "https://plus.google.com/share?url=" + currentURL;
7288 break;
7289 case 'TWITTER':
7290 // shareURL="https://twitter.com/share?url="+currentURL+"&text="+currentTitle;
7291 shareURL = 'https://twitter.com/intent/tweet?text=' + currentTitle + 'url=' + currentURL;
7292 break;
7293 case 'PINTEREST':
7294 // shareURL='https://pinterest.com/pin/create/bookmarklet/?media='+currentTn+'&url='+currentURL+'&description='+currentTitle;
7295 shareURL = 'https://pinterest.com/pin/create/button/?media=' + currentTn + '&url=' + currentURL + '&description=' + currentTitle;
7296 break;
7297 case 'TUMBLR':
7298 //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;
7299 shareURL = 'http://www.tumblr.com/share/link?url=' + currentURL + '&name=' + currentTitle;
7300 break;
7301 case 'MAIL':
7302 shareURL = 'mailto:?subject=' + currentTitle + '&body=' + currentURL;
7303 break;
7304 default:
7305 found = false;
7306 break;
7307 }
7308
7309 if( found ) {
7310 window.open(shareURL, "" , "height=550,width=500,left=100,top=100,menubar=0" );
7311 G.popup.close();
7312 // $popup.remove();
7313 }
7314
7315 });
7316 }
7317
7318 // build a modal popup
7319 function Popup(title, content, align) {
7320 var pp = '<div class="nGY2Popup" style="opacity:0;"><div class="nGY2PopupContent' + align + '">';
7321 pp += '<div class="nGY2PopupCloseButton">' + G.O.icons.buttonClose + '</div>';
7322 pp += '<div class="nGY2PopupTitle">' + title + '</div>';
7323 pp += content;
7324 pp += '</div></div>';
7325
7326 G.popup.$elt = jQuery(pp).appendTo('body');
7327 setElementOnTop( G.VOM.$viewer, G.popup.$elt);
7328
7329 G.popup.isDisplayed = true;
7330
7331 var tweenable = new NGTweenable();
7332 tweenable.tween({
7333 from: { opacity: 0 },
7334 to: { opacity: 1 },
7335 easing: 'easeInOutSine',
7336 duration: 250,
7337 step: function (state, att) {
7338 G.popup.$elt.css( state );
7339 }
7340 });
7341
7342 G.popup.$elt.find('.nGY2PopupCloseButton').on('click', function(e) {
7343 e.stopPropagation();
7344 G.popup.close();
7345 });
7346
7347 }
7348
7349
7350 function GalleryMouseEnter(e) {
7351 if( !G.VOM.viewerDisplayed && G.GOM.albumIdx != -1 ) {
7352 var r = GalleryEventRetrieveElementl(e, true);
7353 // if( r.action == 'OPEN' && r.GOMidx != -1 ) {
7354 if( r.GOMidx != -1 ) {
7355 var target = e.target || e.srcElement;
7356 // if( target.getAttribute('class') != 'nGY2GThumbnail' ) { return; }
7357 ThumbnailHover(r.GOMidx);
7358 }
7359 }
7360 }
7361
7362 function GalleryMouseLeave(e) {
7363 if( !G.VOM.viewerDisplayed && G.GOM.albumIdx != -1 ) {
7364 var r = GalleryEventRetrieveElementl(e, true);
7365 if( r.GOMidx != -1 ) {
7366 var target = e.target || e.srcElement;
7367 // if( target.getAttribute('class') != 'nGY2GThumbnail' ) { return; }
7368 ThumbnailHoverOut(r.GOMidx);
7369 }
7370 }
7371 }
7372
7373 function GalleryEventRetrieveElementl( e, ignoreSubItems ) {
7374 var r = { action: 'NONE', GOMidx: -1 };
7375
7376 if( e == undefined ) {
7377 return r;
7378 }
7379 var target = e.target || e.srcElement;
7380 while( target != G.$E.conTnParent[0] ) { // loop element parent up to find the thumbnail element
7381 if( jQuery(target).hasClass('nGY2GThumbnail') ) {
7382 if( r.action == 'NONE' ) {
7383 r.action = 'OPEN';
7384 }
7385 r.GOMidx = jQuery(target).data('index');
7386 return r;
7387 }
7388 // if( !ignoreSubItems && jQuery(target).hasClass('nGY2GThumbnailIcon') ) {
7389 if( !ignoreSubItems ) {
7390 var a = jQuery(target).data('ngy2action');
7391 if( a != '' && a != undefined ) {
7392 r.action = a;
7393 }
7394 }
7395 if( target.parentNode == null ) {
7396 return r;
7397 }
7398 target = target.parentNode;
7399 }
7400 return r;
7401 }
7402
7403
7404 // Open one thumbnail
7405 function ThumbnailOpen( idx, ignoreSelected ) {
7406 var item = G.I[idx];
7407
7408 var fu = G.O.fnThumbnailClicked;
7409 if( fu !== null ) {
7410 typeof fu == 'function' ? fu(item.$elt, item) : window[fu](item.$elt, item);
7411 }
7412
7413 // open URL
7414 if( item.destinationURL !== undefined && item.destinationURL.length > 0 ) {
7415 window.location = item.destinationURL;
7416 return;
7417 }
7418
7419 switch( item.kind ) {
7420 case 'image':
7421 if( ignoreSelected === false && G.GOM.nbSelected > 0 ) {
7422 ThumbnailSelectionToggle(idx);
7423 }
7424 else {
7425 // display image
7426 DisplayPhotoIdx(idx);
7427 }
7428 break;
7429 case 'album':
7430 if( ignoreSelected === false && G.GOM.nbSelected > 0 ) {
7431 ThumbnailSelectionToggle(idx);
7432 }
7433 else {
7434 DisplayAlbum('-1', item.GetID());
7435 }
7436 break;
7437 case 'albumUp':
7438 var parent = NGY2Item.Get(G, item.albumID);
7439 DisplayAlbum('-1', parent.albumID);
7440 break;
7441 }
7442 }
7443
7444
7445 // Open link to original image (new window)
7446 function OpenOriginal( item ) {
7447 switch( G.O.kind ) {
7448 case 'flickr':
7449 var sU = 'https://www.flickr.com/photos/' + G.O.userID + '/' + item.GetID();
7450 if( item.albumID != '0' ) {
7451 sU += '/in/album-' + item.albumID + '/';
7452 }
7453 window.open(sU, '_blank');
7454 break;
7455 case 'picasa':
7456 case 'google':
7457 case 'google2':
7458 // no more working since Google changed the access to Google Photos in 2017
7459 // var sU='https://plus.google.com/photos/'+G.O.userID+'/albums/'+item.albumID+'/'+item.GetID();
7460 // window.open(sU,'_blank');
7461 // break;
7462 default:
7463 var sU = item.responsiveURL();
7464 window.open(sU, '_blank');
7465 break;
7466 }
7467 }
7468
7469 // Display one photo (with internal or external viewer)
7470 function DisplayPhotoIdx( ngy2ItemIdx ) {
7471
7472 if( !G.O.thumbnailOpenImage ) { return; }
7473
7474 if( G.O.thumbnailOpenOriginal ) {
7475 // Open link to original image
7476 OpenOriginal( G.I[ngy2ItemIdx] );
7477 return;
7478 }
7479
7480 var items = [];
7481 G.VOM.currItemIdx = 0;
7482 G.VOM.items = [];
7483 G.VOM.albumID = G.I[ngy2ItemIdx].albumID;
7484
7485 var vimg = new VImg(ngy2ItemIdx);
7486 G.VOM.items.push(vimg);
7487 items.push(G.I[ngy2ItemIdx]);
7488 //TODO -> danger? -> pourquoi reconstruire la liste si d�j� ouvert (back/forward)
7489 var l = G.I.length;
7490 for( var idx = ngy2ItemIdx+1; idx < l ; idx++) {
7491 var item = G.I[idx];
7492 if( item.kind == 'image' && item.isToDisplay(G.VOM.albumID) && item.destinationURL == '' ) {
7493 var vimg = new VImg(idx);
7494 G.VOM.items.push(vimg);
7495 items.push(item);
7496 }
7497 }
7498 var last = G.VOM.items.length;
7499 var cnt = 1;
7500 for( var idx = 0; idx < ngy2ItemIdx ; idx++) {
7501 var item = G.I[idx];
7502 if( item.kind == 'image' && item.isToDisplay(G.VOM.albumID) && item.destinationURL == '' ) {
7503 var vimg = new VImg(idx);
7504 vimg.mediaNumber = cnt;
7505 G.VOM.items.push(vimg);
7506 items.push(item);
7507 cnt++;
7508 }
7509 }
7510 for( var i = 0; i < last; i++ ) {
7511 G.VOM.items[i].mediaNumber = cnt;
7512 cnt++;
7513 }
7514
7515 // opens media with external viewer
7516 var fu = G.O.fnThumbnailOpen;
7517 if( fu !== null ) {
7518 typeof fu == 'function' ? fu(items) : window[fu](items);
7519 return;
7520 }
7521
7522 // use internal viewer
7523 if( !G.VOM.viewerDisplayed ) {
7524 // build viewer and display
7525 OpenInternalViewer();
7526 }
7527 else {
7528 // viewer already displayed -> display new media in current viewer
7529 G.VOM.$mediaCurrent.empty();
7530 G.VOM.$mediaCurrent.append(G.VOM.NGY2Item(0).mediaMarkup);
7531 ViewerSetMediaVisibility(G.VOM.NGY2Item(1), G.VOM.$mediaNext, 0);
7532 ViewerSetMediaVisibility(G.VOM.NGY2Item(-1), G.VOM.$mediaPrevious, 0);
7533 if( G.VOM.NGY2Item(0).mediaKind == 'img' ) {
7534 G.VOM.ImageLoader.loadImage(VieweImgSizeRetrieved, G.VOM.NGY2Item(0));
7535 }
7536 // G.VOM.$mediaCurrent.css({ opacity:0 }).attr('src','');
7537 // G.VOM.ImageLoader.loadImage(VieweImgSizeRetrieved, G.VOM.NGY2Item(0));
7538 // G.VOM.$mediaCurrent.children().eq(0).attr('src',G.emptyGif).attr('src', G.VOM.NGY2Item(0).responsiveURL());
7539 DisplayInternalViewer(0, '');
7540 }
7541 }
7542
7543 function ViewerZoomStart() {
7544 if( G.O.viewerZoom && !G.VOM.viewerMediaIsChanged ) {
7545 var item=G.VOM.NGY2Item(0);
7546 if( item.imageHeight > 0 && item.imageWidth > 0 ) {
7547 if( G.VOM.zoom.isZooming === false ) {
7548 // default zoom
7549 G.VOM.zoom.userFactor = 1;
7550 G.VOM.zoom.isZooming = true;
7551 }
7552 return true;
7553 }
7554 }
7555 }
7556
7557 function ViewerZoomIn( zoomIn ) {
7558 if( zoomIn ) {
7559 // zoom in
7560 G.VOM.zoom.userFactor+=0.1;
7561 ViewerZoomMax();
7562 }
7563 else {
7564 // zoom out
7565 G.VOM.zoom.userFactor-=0.1;
7566 ViewerZoomMin();
7567 }
7568 ViewerMediaSetPosAndZoom();
7569 }
7570
7571 function ViewerZoomMax() {
7572 if( G.VOM.zoom.userFactor > 3 ) {
7573 G.VOM.zoom.userFactor = 3;
7574 }
7575 }
7576 function ViewerZoomMin() {
7577 if( G.VOM.zoom.userFactor < 0.2 ) {
7578 G.VOM.zoom.userFactor = 0.2;
7579 }
7580 }
7581
7582
7583
7584 // Set position and size of all 3 media containers
7585 function ViewerMediaSetPosAndZoom() {
7586
7587 if( !G.VOM.zoom.isZooming ) {
7588 G.VOM.zoom.userFactor = 1;
7589 }
7590 ViewerMediaSetPosAndZoomOne( G.VOM.NGY2Item(0), G.VOM.$mediaCurrent, true );
7591 ViewerMediaSetPosAndZoomOne( G.VOM.NGY2Item(-1), G.VOM.$mediaPrevious, false );
7592 ViewerMediaSetPosAndZoomOne( G.VOM.NGY2Item(1), G.VOM.$mediaNext, false );
7593 }
7594
7595
7596
7597 // Media which is not IMG -> center and set size
7598 function ViewerMediaCenterNotImg( $mediaContainer ) {
7599 var $media = $mediaContainer.children().eq(0);
7600 $media.css( {'height': '80%' });
7601 $media.css( {'width': '90%' });
7602 $media[0].style[G.CSStransformName] = 'translate(0px, "50%") ';
7603 }
7604
7605 // Set position and size of ONE media container
7606 function ViewerMediaSetPosAndZoomOne(item, $img, isCurrent ) {
7607
7608 if( item.mediaKind != 'img' ) {
7609 ViewerMediaCenterNotImg($img);
7610 return;
7611 }
7612
7613 if( item.imageHeight == 0 || item.imageWidth == 0 ) {
7614 ViewerSetMediaVisibility( item, $img, 0 );
7615 return;
7616 }
7617
7618 // part 1: set the image size
7619 var zoomUserFactor = isCurrent == true ? G.VOM.zoom.userFactor : 1;
7620
7621 var dpr = 1;
7622 if( G.O.viewerImageDisplay == 'bestImageQuality' ) {
7623 dpr = window.devicePixelRatio;
7624 }
7625
7626 // retrieve the base zoom factor (image fill screen)
7627 var zoomBaseFactorW = (G.VOM.window.lastWidth - G.VOM.padding.V) / (item.imageWidth / dpr);
7628 var zoomBaseFactorH = (G.VOM.window.lastHeight - G.VOM.padding.H) / (item.imageHeight / dpr);
7629 var zoomBaseFactor = Math.min(zoomBaseFactorW, zoomBaseFactorH);
7630 if( zoomBaseFactor > 1 && G.O.viewerImageDisplay != 'upscale' ) {
7631 // no upscale
7632 zoomBaseFactor = 1;
7633 }
7634
7635 var imageCurrentHeight = (item.imageHeight / dpr) * zoomUserFactor * zoomBaseFactor;
7636 var imageCurrentWidth = (item.imageWidth / dpr) * zoomUserFactor * zoomBaseFactor;
7637 $img.children().eq(0).css( {'height': imageCurrentHeight });
7638 $img.children().eq(0).css( {'width': imageCurrentWidth });
7639
7640 // retrieve posX/Y to center image
7641 var posX = 0;
7642 if( imageCurrentWidth > G.VOM.window.lastWidth ) {
7643 posX = -(imageCurrentWidth - G.VOM.window.lastWidth)/2;
7644 }
7645 var posY = 0;
7646 if( imageCurrentHeight > G.VOM.window.lastHeight ) {
7647 posY = ( imageCurrentHeight - G.VOM.window.lastHeight ) / 2;
7648 }
7649 posY = 0; // actually, it seems that the image is always centered vertically -> so no need to to anything
7650
7651 // Part 2: set the X/Y position (for zoom/pan)
7652 if( isCurrent ) {
7653 if( !G.VOM.zoom.isZooming ) {
7654 G.VOM.panPosX = 0;
7655 G.VOM.panPosY = 0;
7656 }
7657 G.VOM.zoom.posX = posX;
7658 G.VOM.zoom.posY = posY;
7659 ViewerImagePanSetPosition(G.VOM.panPosX, G.VOM.panPosY, $img, false);
7660 }
7661 // else {
7662 //$img[0].style[G.CSStransformName]= 'translate3D('+ posX+'px, '+ posY+'px, 0) ';
7663 // }
7664
7665 }
7666
7667 // position the image depending on the zoom factor and the pan X/Y position
7668 // IMG is the only media supporting zoom/pan
7669 function ViewerImagePanSetPosition(posX, posY, imageContainer, savePosition ) {
7670 if( savePosition ) {
7671 G.VOM.panPosX = posX;
7672 G.VOM.panPosY = posY;
7673 }
7674
7675 posX += G.VOM.zoom.posX;
7676 posY += G.VOM.zoom.posY;
7677 imageContainer.children()[0].style[G.CSStransformName]= 'translate('+ posX + 'px, '+ posY + 'px) ';
7678 }
7679
7680
7681 // display media with internal viewer
7682 function OpenInternalViewer( ) {
7683
7684 G.VOM.viewerDisplayed = true;
7685 G.GOM.firstDisplay = false;
7686
7687 G.VOM.saveOverflowX = window.getComputedStyle(document.body)['overflow-x'];
7688 G.VOM.saveOverflowY = window.getComputedStyle(document.body)['overflow-y'];
7689 jQuery('body').css({ overflow: 'hidden' }); //avoid scrollbars
7690
7691 G.VOM.$cont = jQuery('<div class="nGY2 nGY2ViewerContainer" style="opacity:1"></div>').appendTo('body');
7692
7693 SetViewerTheme();
7694
7695 G.VOM.$viewer = jQuery('<div class="nGY2Viewer" style="opacity:0" itemscope itemtype="http://schema.org/ImageObject"></div>').appendTo( G.VOM.$cont );
7696 G.VOM.$viewer.css({ msTouchAction: 'none', touchAction: 'none' }); // avoid pinch zoom
7697
7698 G.VOM.currItemIdx = 0;
7699 var sMedia = '<div class="nGY2ViewerMediaPan">' + G.VOM.NGY2Item(-1).mediaMarkup + '</div>'; // previous media
7700 sMedia += '<div class="nGY2ViewerMediaPan">' + G.VOM.NGY2Item(0).mediaMarkup + '</div>'; // current media
7701 sMedia += '<div class="nGY2ViewerMediaPan">' + G.VOM.NGY2Item(1).mediaMarkup + '</div>'; // next media
7702
7703 var sNav = '';
7704 var iconP = G.O.icons.viewerImgPrevious;
7705 if( iconP != undefined && iconP != '') {
7706 sNav += '<div class="nGY2ViewerAreaPrevious ngy2viewerToolAction" data-ngy2action="previous">' + iconP + '</div>';
7707 }
7708 var iconN = G.O.icons.viewerImgNext;
7709 if( iconN != undefined && iconN != '') {
7710 sNav += '<div class="nGY2ViewerAreaNext ngy2viewerToolAction" data-ngy2action="next">' + iconN + '</div>';
7711 }
7712
7713 G.VOM.$content = jQuery('<div class="nGY2ViewerContent">' + sMedia + sNav + '</div>').appendTo( G.VOM.$viewer );
7714
7715 var $mediaPan = G.VOM.$content.find('.nGY2ViewerMediaPan');
7716 G.VOM.$mediaPrevious = $mediaPan.eq(0); // pointer to previous media container
7717 G.VOM.$mediaCurrent = $mediaPan.eq(1); // pointer to current media container
7718 G.VOM.$mediaNext = $mediaPan.eq(2); // pointer to next media container
7719
7720 G.VOM.ImageLoader.loadImage( VieweImgSizeRetrieved, G.VOM.NGY2Item(0) );
7721 G.VOM.ImageLoader.loadImage( VieweImgSizeRetrieved, G.VOM.NGY2Item(-1) );
7722 G.VOM.ImageLoader.loadImage( VieweImgSizeRetrieved, G.VOM.NGY2Item(1) );
7723
7724 G.VOM.padding.H = parseInt(G.VOM.$content.css("padding-left")) + parseInt(G.VOM.$content.css("padding-right"));
7725 G.VOM.padding.V = parseInt(G.VOM.$content.css("padding-top")) + parseInt(G.VOM.$content.css("padding-bottom"));
7726
7727 // build media toolbar container
7728 var vtbBg1 = '';
7729 var vtbBg2 = ' toolbarBackground';
7730 if( G.O.viewerToolbar.fullWidth ) {
7731 vtbBg1 = ' toolbarBackground';
7732 vtbBg2 = '';
7733 }
7734 var vtbAlign = 'text-align:center;';
7735 switch ( G.O.viewerToolbar.align ) {
7736 case 'left':
7737 vtbAlign = 'text-align:left;';
7738 break;
7739 case 'right':
7740 vtbAlign = 'text-align:right;';
7741 break;
7742 }
7743 var sTB = '<div class="toolbarContainer nGEvent' + vtbBg1 + '" style="visibility:' +(G.O.viewerToolbar.display ? "visible" : "hidden")+';'+vtbAlign+'"><div class="toolbar nGEvent' + vtbBg2 + '"></div></div>';
7744 G.VOM.$toolbar = jQuery(sTB).appendTo(G.VOM.$viewer);
7745
7746 if( G.VOM.toolbarMode == 'min' || (G.O.viewerToolbar.autoMinimize > 0 && G.O.viewerToolbar.autoMinimize >= G.GOM.cache.viewport.w) ) {
7747 ViewerToolbarForVisibilityMin();
7748 }
7749 else {
7750 ViewerToolbarForVisibilityStd();
7751 }
7752
7753 // top-left toolbar
7754 if( G.O.viewerTools.topLeft != '' ) {
7755 var sTopLeft = '<div class="nGY2ViewerToolsTopLeft nGEvent"><div class="toolbar nGEvent">';
7756 var sTL = G.O.viewerTools.topLeft.split(',');
7757 for( var i = 0, sTLL = sTL.length; i < sTLL; i++) {
7758 sTopLeft += ToolbarAddElt( sTL[i] );
7759 }
7760 sTopLeft += '</div></div>';
7761 G.VOM.$toolbarTL = jQuery(sTopLeft).appendTo(G.VOM.$viewer);
7762 }
7763 // top-right toolbar
7764 if( G.O.viewerTools.topRight != '' ) {
7765 var sTopRight = '<div class="nGY2ViewerToolsTopRight nGEvent"><div class="toolbar nGEvent">';
7766 var sTR = G.O.viewerTools.topRight.split(',');
7767 for( var i = 0, sTRL = sTR.length; i < sTRL; i++) {
7768 sTopRight += ToolbarAddElt( sTR[i] );
7769 }
7770 sTopRight += '</div></div>';
7771 G.VOM.$toolbarTR = jQuery(sTopRight).appendTo(G.VOM.$viewer);
7772 }
7773
7774 // set the events handler for toolbars
7775 ViewerToolsOn();
7776
7777 // display logo
7778 if( G.O.viewerDisplayLogo ) { G.$E.vwLogo=jQuery('<div class="nGY2 nGY2ViewerLogo"></div>').appendTo(G.VOM.$viewer); }
7779
7780 // Go to fullscreen mode
7781 if( ngscreenfull.enabled && G.O.viewerFullscreen ) { ngscreenfull.request(); }
7782
7783 setElementOnTop('', G.VOM.$viewer);
7784 ResizeInternalViewer(true);
7785 G.VOM.timeImgChanged = new Date().getTime();
7786
7787 // viewer display transition
7788 var tweenable = new NGTweenable();
7789 tweenable.tween({
7790 from: { opacity: 0, posY: G.VOM.window.lastHeight*.5 },
7791 to: { opacity: 1, posY: 0 },
7792 delay: 30,
7793 duration: 500,
7794 easing: 'easeOutQuart',
7795 step: function (state) {
7796 G.VOM.$viewer.css('opacity', state.opacity);
7797 G.VOM.$viewer[0].style[G.CSStransformName] = 'translateY(' + (state.posY) + 'px) ';
7798 }
7799 });
7800
7801 // stop click propagation on media ==> if the user clicks outside of an media, the viewer is closed
7802 // --> no more supported since v2.0.0
7803 // G.VOM.$viewer.find('img').on('click', function (e) { e.stopPropagation(); });
7804
7805
7806 ViewerMediaPanX(0);
7807 ViewerSetEvents();
7808
7809 DisplayInternalViewer(0, '');
7810
7811 if( G.O.slideshowAutoStart ) {
7812 G.VOM.playSlideshow = false;
7813 SlideshowToggle();
7814 }
7815 }
7816
7817 function ViewerEvents() {
7818 if( !G.VOM.viewerDisplayed || G.VOM.viewerMediaIsChanged || G.VOM.NGY2Item(0).mediaKind != 'img') {
7819 // ignore fired event if viewer not displayed or if currently changed or if current media not an image
7820 return false;
7821 }
7822 return true;
7823 }
7824
7825
7826 // viewer gesture handling
7827 function ViewerSetEvents() {
7828
7829 if( G.VOM.hammertime == null ) {
7830
7831 G.VOM.hammertime = new NGHammer.Manager(G.VOM.$cont[0], {
7832 recognizers: [
7833 [NGHammer.Pinch, { enable: true }],
7834 [NGHammer.Pan, { direction: NGHammer.DIRECTION_ALL }]
7835 ]
7836 });
7837
7838 // PAN
7839 G.VOM.hammertime.on('pan', function(ev) {
7840 if( !ViewerEvents() ) { return; }
7841
7842 if( G.VOM.zoom.isZooming ) {
7843 // pan zoomed image
7844 ViewerImagePanSetPosition(G.VOM.panPosX+ev.deltaX, G.VOM.panPosY+ev.deltaY, G.VOM.$mediaCurrent, false);
7845 if( G.VOM.toolbarsDisplayed == true ) {
7846 G.VOM.toolsHide();
7847 }
7848 }
7849 else {
7850 if( ev.deltaY > 50 && Math.abs(ev.deltaX) < 50 ) {
7851 // pan viewer down
7852 ViewerMediaPanX( 0 );
7853 var dist=Math.min(ev.deltaY, 200);
7854 G.VOM.$viewer[0].style[G.CSStransformName] = 'translateY(' + dist + 'px) ';
7855 G.VOM.$viewer.css('opacity', 1-dist/200/2);
7856 }
7857 else {
7858 // pan media left/right
7859 ViewerMediaPanX( ev.deltaX );
7860 G.VOM.$viewer[0].style[G.CSStransformName] = 'translateY(0px)';
7861 G.VOM.$viewer.css('opacity', 1);
7862 }
7863 }
7864 });
7865
7866 // PAN END
7867 G.VOM.hammertime.on('panend', function(ev) {
7868 if( !ViewerEvents() ) { return; }
7869 if( G.VOM.zoom.isZooming ) {
7870 G.VOM.timeImgChanged = new Date().getTime();
7871 ViewerImagePanSetPosition(G.VOM.panPosX+ev.deltaX, G.VOM.panPosY+ev.deltaY, G.VOM.$mediaCurrent, true);
7872 }
7873 else {
7874 if( ev.deltaY > 50 && Math.abs(ev.deltaX) < 50 ) {
7875 // close viewer
7876 CloseInternalViewer(G.VOM.currItemIdx);
7877 }
7878 else {
7879 if( Math.abs( ev.deltaX ) < 50 ) {
7880 ViewerMediaPanX(0);
7881 }
7882 else {
7883 ev.deltaX > 50 ? DisplayPreviousMedia() : DisplayNextMedia();
7884 }
7885 }
7886 }
7887 });
7888
7889 if( G.O.viewerZoom ) {
7890
7891 G.VOM.hammertime.add( new NGHammer.Tap({ event: 'doubletap', taps: 2 }) );
7892 G.VOM.hammertime.add( new NGHammer.Tap({ event: 'singletap' }) );
7893 G.VOM.hammertime.get('doubletap').recognizeWith('singletap');
7894 G.VOM.hammertime.get('singletap').requireFailure('doubletap');
7895
7896 // single tap -> next/previous media
7897 G.VOM.hammertime.on('singletap', function(ev) {
7898 if( !ViewerEvents() ) { return; }
7899 StopPropagationPreventDefault(ev.srcEvent);
7900 if( G.VOM.toolbarsDisplayed == false ) {
7901 debounce( ViewerToolsUnHide, 400, false)();
7902 }
7903 else {
7904 if( ev.target.className.indexOf('nGY2ViewerMedia') !== -1 ) {
7905 if( ev.srcEvent.pageX < (G.GOM.cache.viewport.w/2) ) {
7906 DisplayPreviousMedia();
7907 }
7908 else {
7909 DisplayNextMedia();
7910 }
7911 }
7912 }
7913 });
7914
7915 // double tap -> zoom
7916 G.VOM.hammertime.on('doubletap', function(ev) {
7917 if( !ViewerEvents() ) { return; }
7918 StopPropagationPreventDefault(ev.srcEvent);
7919
7920 if( ev.target.className.indexOf('nGY2ViewerMedia') !== -1 ) {
7921 // double tap only on image
7922 if( G.VOM.zoom.isZooming ) {
7923 G.VOM.zoom.isZooming = false;
7924 G.VOM.zoom.userFactor = 1;
7925 ResizeInternalViewer(true);
7926 }
7927 else {
7928 if( ViewerZoomStart() ) {
7929 G.VOM.zoom.userFactor = 1.5;
7930 ViewerMediaSetPosAndZoom();
7931 }
7932 }
7933 }
7934 });
7935
7936 // pinch end
7937 G.VOM.hammertime.on('pinchend', function(ev) {
7938 ev.srcEvent.stopPropagation();
7939 ev.srcEvent.preventDefault(); // cancel mouseenter event
7940 G.VOM.timeImgChanged = new Date().getTime();
7941 });
7942 G.VOM.hammertime.on('pinch', function(ev) {
7943 ev.srcEvent.stopPropagation();
7944 ev.srcEvent.preventDefault(); // cancel mouseenter event
7945
7946 if( ViewerZoomStart() ) {
7947 G.VOM.zoom.userFactor = ev.scale;
7948 ViewerZoomMax();
7949 ViewerZoomMin();
7950 ViewerMediaSetPosAndZoom(); // center media
7951 }
7952 });
7953 }
7954 else {
7955 // no zoom -> click/tap on image to go to next/previous one
7956 G.VOM.hammertime.on('tap', function(ev) {
7957 if( !ViewerEvents() ) { return; }
7958 StopPropagationPreventDefault(ev.srcEvent);
7959 if( G.VOM.toolbarsDisplayed == false ){
7960 // display tools on tap if hidden
7961 debounce( ViewerToolsUnHide, 400, false)();
7962 }
7963 else {
7964 // display next/previous image if tools not hidden
7965 if( ev.target.className.indexOf('nGY2ViewerMedia') !== -1 ) {
7966 if( ev.srcEvent.pageX < (G.GOM.cache.viewport.w/2) ) {
7967 DisplayPreviousMedia();
7968 }
7969 else {
7970 DisplayNextMedia();
7971 }
7972 }
7973 }
7974
7975 });
7976 }
7977 }
7978 }
7979
7980 function StopPropagationPreventDefault(e) {
7981 e.stopPropagation();
7982 e.preventDefault();
7983 }
7984
7985 // Hide toolbars on user inactivity
7986 function ViewerToolsHide() {
7987 if( G.VOM.viewerDisplayed ) {
7988 G.VOM.toolbarsDisplayed = false;
7989 ViewerToolsOpacity(0);
7990 }
7991 }
7992
7993 function ViewerToolsUnHide() {
7994 if( G.VOM.viewerDisplayed ) {
7995 G.VOM.toolbarsDisplayed = true;
7996 ViewerToolsOpacity(1);
7997 G.VOM.toolsHide();
7998 }
7999 }
8000
8001 function ViewerToolsOpacity( op ) {
8002 G.VOM.$toolbar.css('opacity', op);
8003 G.VOM.$toolbarTL.css('opacity', op);
8004 G.VOM.$toolbarTR.css('opacity', op);
8005 G.VOM.$content.find('.nGY2ViewerAreaNext').css('opacity', op);
8006 G.VOM.$content.find('.nGY2ViewerAreaPrevious').css('opacity', op);
8007 }
8008
8009
8010
8011 function ViewerToolsOn() {
8012 // removes all events
8013 G.VOM.$viewer.off('touchstart click', '.ngy2viewerToolAction', ViewerToolsAction);
8014
8015 // action button
8016 G.VOM.$viewer.on('touchstart click', '.ngy2viewerToolAction', ViewerToolsAction);
8017 }
8018
8019
8020 // Actions of the buttton/elements
8021 function ViewerToolsAction(e) {
8022 // delay to avoid twice handling on smartphone/tablet (both touchstart click events are fired)
8023 if( (new Date().getTime()) - G.timeLastTouchStart < 300 ) { return; }
8024 G.timeLastTouchStart = new Date().getTime();
8025
8026 var $this = $(this);
8027 var ngy2action = $this.data('ngy2action');
8028 if( ngy2action == undefined ) { return; }
8029 switch( ngy2action ) {
8030 case 'next':
8031 StopPropagationPreventDefault(e);
8032 DisplayNextMedia();
8033 break;
8034 case 'previous':
8035 StopPropagationPreventDefault(e);
8036 DisplayPreviousMedia();
8037 break;
8038 case 'playPause':
8039 e.stopPropagation();
8040 SlideshowToggle();
8041 break;
8042 case 'zoomIn':
8043 StopPropagationPreventDefault(e);
8044 if( ViewerZoomStart() ) { ViewerZoomIn( true ); }
8045 break;
8046 case 'zoomOut':
8047 StopPropagationPreventDefault(e);
8048 if( ViewerZoomStart() ) { ViewerZoomIn( false ); }
8049 break;
8050 case 'minimize':
8051 // toggle toolbar visibility
8052 StopPropagationPreventDefault(e);
8053 if( G.VOM.toolbarMode == 'std' ) {
8054 ViewerToolbarForVisibilityMin();
8055 }
8056 else {
8057 ViewerToolbarForVisibilityStd();
8058 }
8059 break;
8060 case 'fullScreen':
8061 // Toggle viewer fullscreen mode on/off
8062 e.stopPropagation();
8063 if( ngscreenfull.enabled ) {
8064 ngscreenfull.toggle();
8065 }
8066 break;
8067 case 'info':
8068 e.stopPropagation();
8069 ItemDisplayInfo(G.VOM.NGY2Item(0));
8070 break;
8071 case 'close':
8072 StopPropagationPreventDefault(e);
8073 if( (new Date().getTime()) - G.VOM.timeImgChanged < 400 ) { return; }
8074 CloseInternalViewer(G.VOM.currItemIdx);
8075 break;
8076 case 'download':
8077 StopPropagationPreventDefault(e);
8078 DownloadImage(G.VOM.items[G.VOM.currItemIdx].ngy2ItemIdx);
8079 break;
8080 case 'share':
8081 StopPropagationPreventDefault(e);
8082 PopupShare(G.VOM.items[G.VOM.currItemIdx].ngy2ItemIdx);
8083 break;
8084 case 'linkOriginal':
8085 StopPropagationPreventDefault(e);
8086 OpenOriginal( G.VOM.NGY2Item(0) );
8087 break;
8088 }
8089
8090 // custom button
8091 var fu = G.O.fnImgToolbarCustClick;
8092 if( ngy2action.indexOf('custom') == 0 && fu !== null ) {
8093 typeof fu == 'function' ? fu(ngy2action, $this, G.VOM.NGY2Item(0)) : window[fu](ngy2action, $this, G.VOM.NGY2Item(0));
8094 }
8095 }
8096
8097
8098 // Display photo infos
8099 function ItemDisplayInfo( item) {
8100
8101 var content = '<div class="nGY2PopupOneItemText">' + item.title + '</div>';
8102 content += '<div class="nGY2PopupOneItemText">' + item.description + '</div>';
8103 if( item.author != '' ) {
8104 content += '<div class="nGY2PopupOneItemText">' + G.O.icons.user + ' ' + item.author + '</div>';
8105 }
8106 if( item.exif.model != '' ) {
8107 content += '<div class="nGY2PopupOneItemText">' + G.O.icons.config + ' ' + item.exif.model + '</div>';
8108 }
8109 var sexif = '';
8110 sexif += item.exif.flash == '' ? '' : ' &nbsp; ' + item.exif.flash;
8111 sexif += item.exif.focallength == '' ? '' : ' &nbsp; ' + item.exif.focallength+'mm';
8112 sexif += item.exif.fstop == '' ? '' : ' &nbsp; f' + item.exif.fstop;
8113 sexif += item.exif.exposure == '' ? '' : ' &nbsp; ' + item.exif.exposure+'s';
8114 sexif += item.exif.iso == '' ? '' : ' &nbsp; ' + item.exif.iso+' ISO';
8115 if( item.exif.time != '' ) {
8116 // var date = new Date(parseInt(item.exif.time));
8117 // sexif += ' &nbsp; '+date.toLocaleDateString();
8118 sexif += ' &nbsp; ' + item.exif.time;
8119 }
8120 content += '<div class="nGY2PopupOneItemText">' + sexif + '</div>';
8121
8122 if( item.exif.location != '' ) {
8123 content += '<div class="nGY2PopupOneItemText">'+G.O.icons.location+' <a href="http://maps.google.com/maps?z=12&t=m&q='+encodeURIComponent(item.exif.location)+'" target="_blank">'+item.exif.location+'</a></div>';
8124 // embed google map in iframe (no api key required)
8125 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( item.exif.location ) +'&amp;output=embed"></iframe>';
8126 }
8127
8128 Popup(G.O.icons.viewerInfo, content, 'Left');
8129
8130 }
8131
8132
8133
8134 function ToolbarAddElt( elt ) {
8135 var r = '<div class="ngbt ngy2viewerToolAction ',
8136 e=elt.replace(/^\s+|\s+$/g, ''); // remove trailing/leading whitespace
8137 switch( e ) {
8138 case 'minimizeButton':
8139 var ic = G.O.icons.viewerToolbarMin;
8140 if( G.VOM.toolbarMode == 'min' ) {
8141 ic = G.O.icons.viewerToolbarStd;
8142 }
8143 r += 'minimizeButton nGEvent" data-ngy2action="minimize">'+ic+'</div>';
8144 break;
8145 case 'previousButton':
8146 r += 'previousButton nGEvent" data-ngy2action="previous">'+G.O.icons.viewerPrevious+'</div>';
8147 break;
8148 case 'pageCounter':
8149 r += 'pageCounter nGEvent"></div>';
8150 break;
8151 case 'nextButton':
8152 r += 'nextButton nGEvent" data-ngy2action="next">'+G.O.icons.viewerNext+'</div>';
8153 break;
8154 case 'playPauseButton':
8155 r += 'playButton playPauseButton nGEvent" data-ngy2action="playPause">'+G.O.icons.viewerPlay+'</div>';
8156 break;
8157 case 'downloadButton':
8158 r += 'downloadButton nGEvent" data-ngy2action="download">'+G.O.icons.viewerDownload+'</div>';
8159 break;
8160 case 'zoomButton':
8161 r += 'nGEvent" data-ngy2action="zoomIn">'+G.O.icons.viewerZoomIn+'</div><div class="ngbt ngy2viewerToolAction nGEvent" data-ngy2action="zoomOut">'+G.O.icons.viewerZoomOut+'</div>';
8162 break;
8163 case 'fullscreenButton':
8164 var s = G.O.icons.viewerFullscreenOn;
8165 if( ngscreenfull.enabled && G.VOM.viewerIsFullscreen ) {
8166 s = G.O.icons.viewerFullscreenOff;
8167 }
8168 r += 'setFullscreenButton fullscreenButton nGEvent" data-ngy2action="fullScreen">'+s+'</div>';
8169 break;
8170 case 'infoButton':
8171 r += 'infoButton nGEvent" data-ngy2action="info">'+G.O.icons.viewerInfo+'</div>';
8172 break;
8173 case 'linkOriginalButton':
8174 r += 'linkOriginalButton nGEvent" data-ngy2action="linkOriginal">' + G.O.icons.viewerLinkOriginal + '</div>';
8175 break;
8176 case 'closeButton':
8177 r += 'closeButton nGEvent" data-ngy2action="close">'+G.O.icons.buttonClose+'</div>';
8178 break;
8179 case 'shareButton':
8180 r += 'nGEvent" data-ngy2action="share">'+G.O.icons.viewerShare+'</div>';
8181 break;
8182 case 'label':
8183 r += '"><div class="title nGEvent" itemprop="name"></div><div class="description nGEvent" itemprop="description"></div></div>';
8184 break;
8185 default:
8186 // custom button
8187 if( e.indexOf('custom') == 0 ) {
8188 var t = '';
8189 // content to display from custom script
8190 var fu = G.O.fnImgToolbarCustInit;
8191 if( fu !== null ) {
8192 typeof fu == 'function' ? fu(e) : window[fu](e);
8193 }
8194 if( t == undefined || t == '' ) {
8195 // content from icons
8196 var n = e.substring(6);
8197 t = G.O.icons['viewerCustomTool'+n];
8198 }
8199 r += 'ngy2CustomBtn ' + e + ' nGEvent" data-ngy2action="' + e + '">' + t + '</div>';
8200 }
8201 else {
8202 r = '';
8203 }
8204 break;
8205 }
8206 return r;
8207 }
8208
8209
8210 // toggle slideshow mode on/off
8211 function SlideshowToggle(){
8212 if( G.VOM.playSlideshow ) {
8213 window.clearTimeout(G.VOM.playSlideshowTimerID);
8214 G.VOM.playSlideshow = false;
8215 G.VOM.$viewer.find('.playPauseButton').html(G.O.icons.viewerPlay);
8216 }
8217 else {
8218 G.VOM.playSlideshow = true;
8219 DisplayNextMedia();
8220 G.VOM.$viewer.find('.playPauseButton').html(G.O.icons.viewerPause);
8221 }
8222 }
8223
8224 function ViewerToolbarForVisibilityStd() {
8225 G.VOM.toolbarMode = 'std';
8226
8227 var sTB = '';
8228 var t = G.O.viewerToolbar.standard.split(',');
8229 for( var i = 0, lt = t.length; i < lt; i++) {
8230 sTB += ToolbarAddElt( t[i] );
8231 }
8232 G.VOM.$toolbar.find('.toolbar').html(sTB);
8233 ViewerToolbarElementContent();
8234 }
8235
8236 function ViewerToolbarForVisibilityMin() {
8237 if( G.O.viewerToolbar.minimized == undefined || G.O.viewerToolbar.minimized == '' ) {
8238 ViewerToolbarForVisibilityStd();
8239 }
8240 else {
8241 G.VOM.toolbarMode = 'min';
8242 var sTB = '';
8243 var t = G.O.viewerToolbar.minimized.split(',');
8244 for( var i = 0, lt = t.length; i < lt; i++) {
8245 sTB += ToolbarAddElt( t[i] );
8246 }
8247 G.VOM.$toolbar.find('.toolbar').html(sTB);
8248 ViewerToolbarElementContent();
8249 }
8250 }
8251
8252 function ViewerToolbarElementContent() {
8253
8254 var vomIdx=G.VOM.currItemIdx;
8255 if( vomIdx == null ) { return; }
8256
8257 var item=G.VOM.NGY2Item(0);
8258
8259 // LABEL
8260 var setTxt = false;
8261 // set title
8262 if( item.title !== undefined && item.title != '' ) {
8263 G.VOM.$viewer.find('.ngy2viewerToolAction').find('.title').html(item.title);
8264 setTxt = true;
8265 }
8266 else {
8267 G.VOM.$viewer.find('.ngy2viewerToolAction').find('.title').html('');
8268 }
8269 // set description
8270 if( item.description !== undefined && item.description != '' ) {
8271 G.VOM.$viewer.find('.ngy2viewerToolAction').find('.description').html(item.description);
8272 setTxt = true;
8273 }
8274 else {
8275 G.VOM.$viewer.find('.ngy2viewerToolAction').find('.description').html('');
8276 }
8277
8278 if( setTxt ) {
8279 G.VOM.$viewer.find('.ngy2viewerToolAction').find('.label').show();
8280 }
8281 else {
8282 G.VOM.$viewer.find('.ngy2viewerToolAction').find('.label').hide();
8283 }
8284
8285 // set page number
8286 var viewerMaxImages = G.VOM.items.length;
8287 if( viewerMaxImages > 0 ) {
8288 G.VOM.$viewer.find('.pageCounter').html((G.VOM.items[vomIdx].mediaNumber)+'/'+viewerMaxImages);
8289 }
8290
8291 // custom elements
8292 var $cu = G.VOM.$viewer.find('.ngy2CustomBtn');
8293 var fu = G.O.fnImgToolbarCustDisplay;
8294 if( $cu.length > 0 && fu !== null ) {
8295 typeof fu == 'function' ? fu($cu, item) : window[fu]($cu, item);
8296 }
8297
8298 // set event handlers again
8299 ViewerToolsOn();
8300 }
8301
8302 // Pan the media in the lightbox (left/right)
8303 function ViewerMediaPanX( posX ) {
8304 G.VOM.swipePosX = posX;
8305 if( G.CSStransformName == null ) {
8306 // no pan if CSS transform not supported
8307 // G.VOM.$mediaCurrent.css({ left: posX });
8308 }
8309 else {
8310 G.VOM.$mediaCurrent[0].style[G.CSStransformName] = 'translate(' + posX + 'px, 0px)';
8311
8312
8313 if( G.O.imageTransition == 'swipe' ) {
8314 if( G.VOM.NGY2Item(-1).mediaTransition() ) {
8315 ViewerSetMediaVisibility(G.VOM.NGY2Item(-1), G.VOM.$mediaPrevious, 1);
8316 }
8317 if( G.VOM.NGY2Item(1).mediaTransition() ) {
8318 ViewerSetMediaVisibility(G.VOM.NGY2Item(1), G.VOM.$mediaNext, 1);
8319 }
8320 if( posX > 0 ) {
8321 var dir = G.VOM.window.lastWidth;
8322 if( G.VOM.NGY2Item(-1).mediaTransition() ) {
8323 G.VOM.$mediaPrevious[0].style[G.CSStransformName] = 'translate(' + (-dir + posX) + 'px, 0px)';
8324 }
8325 if( G.VOM.NGY2Item(1).mediaTransition() ) {
8326 G.VOM.$mediaNext[0].style[G.CSStransformName] = 'translate(' + (-dir) + 'px, 0px)';
8327 }
8328 }
8329 else {
8330 var dir = -G.VOM.window.lastWidth;
8331 if( G.VOM.NGY2Item(1).mediaTransition() ) {
8332 G.VOM.$mediaNext[0].style[G.CSStransformName] = 'translate(' + (-dir + posX) + 'px, 0px)';
8333 }
8334 if( G.VOM.NGY2Item(-1).mediaTransition() ) {
8335 G.VOM.$mediaPrevious[0].style[G.CSStransformName] = 'translate(' + (-dir) + 'px, 0px)';
8336 }
8337 }
8338 }
8339
8340
8341 if( G.O.imageTransition == 'slideAppear' ) {
8342 G.VOM.$mediaPrevious[0].style[G.CSStransformName] = '';
8343 G.VOM.$mediaNext[0].style[G.CSStransformName] = '';
8344 if( posX < 0 ) {
8345 var o = (-posX) / G.VOM.window.lastWidth;
8346 if( G.VOM.NGY2Item(1).mediaTransition() ) {
8347 ViewerSetMediaVisibility(G.VOM.NGY2Item(1), G.VOM.$mediaNext, o);
8348 }
8349 if( G.VOM.NGY2Item(-1).mediaTransition() ) {
8350 ViewerSetMediaVisibility(G.VOM.NGY2Item(-1), G.VOM.$mediaPrevious, 0);
8351 }
8352 }
8353 else {
8354 var o = posX / G.VOM.window.lastWidth;
8355 if( G.VOM.NGY2Item(-1).mediaTransition() ) {
8356 ViewerSetMediaVisibility(G.VOM.NGY2Item(-1), G.VOM.$mediaPrevious, o);
8357 }
8358 if( G.VOM.NGY2Item(1).mediaTransition() ) {
8359 ViewerSetMediaVisibility(G.VOM.NGY2Item(1), G.VOM.$mediaNext, 0);
8360 }
8361 }
8362 }
8363 }
8364 }
8365
8366 // Display next image
8367 function DisplayNextMedia() {
8368 if( G.VOM.viewerMediaIsChanged || ((new Date().getTime()) - G.VOM.timeImgChanged < 300) ) { return; }
8369
8370 TriggerCustomEvent('lightboxNextImage');
8371 DisplayInternalViewer(G.VOM.IdxNext(), 'nextImage');
8372 };
8373
8374 // Display previous image
8375 function DisplayPreviousMedia() {
8376 if( G.VOM.viewerMediaIsChanged || ((new Date().getTime()) - G.VOM.timeImgChanged < 300) ) { return; }
8377 if( G.VOM.playSlideshow ) {
8378 SlideshowToggle();
8379 }
8380
8381 TriggerCustomEvent('lightboxPreviousImage');
8382 DisplayInternalViewer(G.VOM.IdxPrevious(), 'previousImage');
8383 };
8384
8385 // Display image (and run animation)
8386 function DisplayInternalViewer( newVomIdx, displayType ) {
8387
8388 if( G.VOM.playSlideshow ) { window.clearTimeout(G.VOM.playSlideshowTimerID); }
8389
8390 var itemOld = G.VOM.NGY2Item(0);
8391 var itemNew = G.I[G.VOM.items[newVomIdx].ngy2ItemIdx];
8392 var $new = (displayType == 'nextImage' ? G.VOM.$mediaNext : G.VOM.$mediaPrevious);
8393 if( displayType == 'nextImage' ) {
8394 ViewerSetMediaVisibility(G.VOM.NGY2Item(-1), G.VOM.$mediaPrevious, 0);
8395 }
8396 else {
8397 ViewerSetMediaVisibility(G.VOM.NGY2Item(1), G.VOM.$mediaNext, 0);
8398 }
8399
8400 G.VOM.timeImgChanged = new Date().getTime();
8401 G.VOM.viewerMediaIsChanged = true;
8402 G.VOM.zoom.isZooming = false;
8403 ResizeInternalViewer(true);
8404
8405 if( G.O.debugMode && console.timeline ) { console.timeline('nanogallery2_viewer'); }
8406
8407 SetLocationHash( itemNew.albumID, itemNew.GetID() );
8408
8409 // animation duration is proportional of the remaining distance
8410 var vP = G.GOM.cache.viewport;
8411 var dur = 400 * (vP.w - Math.abs(G.VOM.swipePosX)) / vP.w;
8412
8413 if( displayType == '' ) {
8414 // first image --> just appear / no slide animation
8415 ViewerSetMediaVisibility(G.VOM.NGY2Item(0), G.VOM.$mediaCurrent, 1);
8416 if( G.CSStransformName == null ) {
8417 // no CSS transform support -> no animation
8418 ViewerSetMediaVisibility(itemNew, $new, 1);
8419 DisplayInternalViewerComplete(displayType, newVomIdx);
8420 }
8421 else {
8422 ViewerSetMediaVisibility(itemNew, $new, 0);
8423 var tweenable = new NGTweenable();
8424 tweenable.tween({
8425 from: { opacity: 0 },
8426 to: { opacity: 1 },
8427 attachment: { dT: displayType, item: itemOld },
8428 easing: 'easeInOutSine',
8429 delay: 30,
8430 duration: 400,
8431 step: function (state, att) {
8432 // using scale is not a good idea on Chrome -> image will be blurred
8433 G.VOM.$content.css('opacity', state.opacity);
8434 },
8435 finish: function (state, att) {
8436 G.VOM.$content.css('opacity', 1);
8437 ViewerToolsUnHide();
8438 DisplayInternalViewerComplete(att.dT, newVomIdx);
8439 }
8440 });
8441 }
8442 }
8443 else {
8444 // animate the image change
8445 switch( G.O.imageTransition.toUpperCase() ) {
8446 case 'SWIPE':
8447 if( G.CSStransformName == null ) {
8448 // no CSS transform support -> no animation
8449 ViewerSetMediaVisibility(itemNew, $new, 1);
8450 ViewerSetMediaVisibility(G.VOM.NGY2Item(0), G.VOM.$mediaCurrent, 1);
8451 DisplayInternalViewerComplete(displayType, newVomIdx);
8452 }
8453 else {
8454 var dir = ( displayType == 'nextImage' ? - vP.w : vP.w );
8455 $new[0].style[G.CSStransformName] = 'translate('+(-dir)+'px, 0px) '
8456 var tweenable = new NGTweenable();
8457 tweenable.tween({
8458 from: { t: G.VOM.swipePosX },
8459 to: { t: (displayType == 'nextImage' ? - vP.w : vP.w) },
8460 attachment: { dT: displayType, $e: $new, item: itemOld, itemNew: itemNew, dir: dir },
8461 delay: 30,
8462 duration: dur,
8463 easing: 'easeInOutSine',
8464 step: function (state, att) {
8465 // current media
8466 ViewerSetMediaVisibility(G.VOM.NGY2Item(0), G.VOM.$mediaCurrent, 1);
8467 G.VOM.$mediaCurrent[0].style[G.CSStransformName] = 'translate(' + state.t + 'px, 0px)';
8468 // new media
8469 if( att.itemNew.mediaTransition() ) {
8470 ViewerSetMediaVisibility(att.itemNew, att.$e, 1);
8471 att.$e[0].style[G.CSStransformName] = 'translate(' + (-att.dir+state.t) + 'px, 0px)';
8472 }
8473 },
8474 finish: function (state, att) {
8475 // current media
8476 G.VOM.$mediaCurrent[0].style[G.CSStransformName]= '';
8477 // new media
8478 att.$e[0].style[G.CSStransformName]= '';
8479 DisplayInternalViewerComplete(att.dT, newVomIdx);
8480 }
8481 });
8482 }
8483 break;
8484
8485 case 'SLIDEAPPEAR':
8486 default:
8487 if( G.CSStransformName == null ) {
8488 // no CSS transform support -> no animation
8489 ViewerSetMediaVisibility(itemNew, $new, 1);
8490 ViewerSetMediaVisibility(G.VOM.NGY2Item(0), G.VOM.$mediaCurrent, 1);
8491 DisplayInternalViewerComplete(displayType, newVomIdx);
8492 }
8493 else {
8494 var dir=(displayType == 'nextImage' ? - vP.w : vP.w);
8495 var op = (Math.abs(G.VOM.swipePosX)) / G.VOM.window.lastWidth;
8496 $new[0].style[G.CSStransformName] = '';
8497 var tweenable = new NGTweenable();
8498 tweenable.tween({
8499 from: { o: op, t: G.VOM.swipePosX },
8500 to: { o: 1, t: (displayType == 'nextImage' ? - vP.w : vP.w) },
8501 attachment: { dT:displayType, $e:$new, item: itemOld, itemNew: itemNew, dir: dir },
8502 delay: 30,
8503 duration: dur,
8504 easing: 'easeInOutSine',
8505 step: function (state, att) {
8506 // current media - translate
8507 G.VOM.$mediaCurrent[0].style[G.CSStransformName]= 'translate('+state.t+'px, 0px)';
8508 // new media - opacity
8509 if( att.itemNew.mediaTransition() ) {
8510 ViewerSetMediaVisibility(att.itemNew, att.$e, state.o);
8511 }
8512 },
8513 finish: function (state, att) {
8514 // current media
8515 G.VOM.$mediaCurrent[0].style[G.CSStransformName]= '';
8516 DisplayInternalViewerComplete(att.dT, newVomIdx);
8517 }
8518 });
8519 }
8520 break;
8521 }
8522 }
8523 }
8524
8525
8526 function DisplayInternalViewerComplete( displayType, newVomIdx ) {
8527 G.VOM.currItemIdx = newVomIdx;
8528
8529 var ngy2item = G.VOM.NGY2Item(0);
8530
8531 ViewerToolbarElementContent();
8532 if( G.O.debugMode && console.timeline ) { console.timelineEnd('nanogallery2_viewer'); }
8533
8534 var fu=G.O.fnImgDisplayed;
8535 if( fu !== null ) {
8536 typeof fu == 'function' ? fu(ngy2item) : window[fu](ngy2item);
8537 }
8538
8539 G.VOM.swipePosX = 0;
8540
8541 if( displayType != '' ) {
8542 // not on first media display
8543 // G.VOM.$mediaCurrent.off("click");
8544 G.VOM.$mediaCurrent.removeClass('imgCurrent');
8545
8546 var $tmp = G.VOM.$mediaCurrent;
8547 switch( displayType ) {
8548 case 'nextImage':
8549 G.VOM.$mediaCurrent = G.VOM.$mediaNext;
8550 G.VOM.$mediaNext = $tmp;
8551 break;
8552 case 'previousImage':
8553 G.VOM.$mediaCurrent = G.VOM.$mediaPrevious;
8554 G.VOM.$mediaPrevious = $tmp;
8555 break;
8556 }
8557 G.VOM.$mediaCurrent.addClass('imgCurrent');
8558 if( ngy2item.mediaKind == 'img' && ngy2item.imageWidth == 0 ) {
8559 ViewerSetMediaVisibility(G.VOM.NGY2Item(0), G.VOM.$mediaCurrent, 0);
8560 }
8561 else {
8562 ViewerSetMediaVisibility(G.VOM.NGY2Item(0), G.VOM.$mediaCurrent, 1);
8563 }
8564 }
8565
8566 // set the new next media
8567 G.VOM.$mediaNext.empty();
8568 G.VOM.$mediaNext.append( G.VOM.NGY2Item(1).mediaMarkup );
8569 ViewerSetMediaVisibility(G.VOM.NGY2Item(1), G.VOM.$mediaNext, 0);
8570 if( G.VOM.NGY2Item(1).mediaKind == 'img' ) {
8571 G.VOM.ImageLoader.loadImage(VieweImgSizeRetrieved, G.VOM.NGY2Item(1));
8572 }
8573 else {
8574 ViewerMediaCenterNotImg(G.VOM.$mediaNext);
8575 }
8576
8577 // set the new previous media
8578 G.VOM.$mediaPrevious.empty();
8579 G.VOM.$mediaPrevious.append(G.VOM.NGY2Item(-1).mediaMarkup);
8580 ViewerSetMediaVisibility(G.VOM.NGY2Item(-1), G.VOM.$mediaPrevious, 0);
8581 if( G.VOM.NGY2Item(-1).mediaKind == 'img' ) {
8582 G.VOM.ImageLoader.loadImage(VieweImgSizeRetrieved, G.VOM.NGY2Item(-1));
8583 }
8584 else {
8585 ViewerMediaCenterNotImg(G.VOM.$mediaPrevious);
8586 }
8587
8588
8589 // slideshow mode - wait until image is loaded to start the delay for next image
8590 if( G.VOM.playSlideshow ) {
8591 G.VOM.$mediaCurrent.children().eq(0).ngimagesLoaded().always( function( instance ) {
8592 if( G.VOM.playSlideshow ) {
8593 // in the meantime the user could have stopped the slideshow
8594 G.VOM.playSlideshowTimerID = window.setTimeout( function(){ DisplayNextMedia(); }, G.VOM.slideshowDelay );
8595 }
8596 });
8597 }
8598
8599 // close viewer when user clicks outside of the image
8600 // G.VOM.$mediaCurrent.on("click",function(e){
8601 // e.stopPropagation();
8602 // if( (new Date().getTime()) - G.VOM.timeImgChanged < 400 ) { return; }
8603 // StopPropagationPreventDefault(e);
8604 // CloseInternalViewer(G.VOM.currItemIdx);
8605 // return false;
8606 // });
8607
8608 ResizeInternalViewer();
8609
8610 G.VOM.viewerMediaIsChanged = false;
8611 TriggerCustomEvent('lightboxImageDisplayed');
8612
8613 }
8614
8615
8616 // Is fired as soon as the size of an image has been retrieved
8617 function VieweImgSizeRetrieved(w, h, item, n) {
8618 item.imageWidth = w;
8619 item.imageHeight = h;
8620
8621 // image sized retrieved for currently displayed media
8622 // if( G.VOM.$mediaCurrent !== null && G.VOM.$mediaCurrent.children().attr('src') == item.responsiveURL() ) {
8623 if( G.VOM.NGY2Item(0) == item ) {
8624 // it is the current displayed media
8625 ViewerSetMediaVisibility(G.VOM.NGY2Item(0), G.VOM.$mediaCurrent, 1);
8626 G.VOM.zoom.userFactor = 1;
8627 }
8628 ViewerMediaSetPosAndZoom();
8629
8630 }
8631
8632 // Viewer - Set the visibility of the media and it's container
8633 function ViewerSetMediaVisibility(item, $media, opacity ) {
8634
8635 if( item.mediaKind == 'img' && item.imageWidth == 0 ) {
8636 // do not display image if width is unknown (--> callback will set the width when know)
8637 // setting opacity to 0 is not enough -> it is mandatory to change also the visibility to hidden to avoid responds to events (click/touch)
8638 $media.children().css({ opacity: 0, visibility: 'hidden' });
8639 $media.css({ opacity: 0, visibility: 'hidden' });
8640 return;
8641 }
8642
8643 if( opacity == 0 ) {
8644 // setting opacity to 0 is not enough -> it is mandatory to change also the visibility to hidden to avoid responds to events (click/touch)
8645 $media.css({ opacity: 0, visibility: 'hidden' });
8646 $media.children().css({ opacity: 0, visibility: 'hidden' });
8647 }
8648 else {
8649 $media.css({ opacity: opacity, visibility: 'visible' });
8650 $media.children().css({ opacity: opacity, visibility: 'visible' });
8651 }
8652 }
8653
8654
8655 // Close the internal lightbox
8656 function CloseInternalViewer( vomIdx ) {
8657
8658 G.VOM.viewerMediaIsChanged = false;
8659
8660 if( G.VOM.viewerDisplayed ) {
8661
8662 // set scrollbar visible again
8663 jQuery('body').css({ overflowX: G.VOM.saveOverflowX, overflowY: G.VOM.saveOverflowY});
8664 // jQuery('body').css({overflow: 'visible'});
8665
8666
8667 if( G.VOM.playSlideshow ) {
8668 window.clearTimeout(G.VOM.playSlideshowTimerID);
8669 G.VOM.playSlideshow = false;
8670 }
8671
8672 // G.VOM.userEvents.removeEventListeners();
8673 // G.VOM.userEvents=null;
8674 G.VOM.hammertime.destroy();
8675 G.VOM.hammertime = null;
8676
8677 if( ngscreenfull.enabled && G.VOM.viewerIsFullscreen ) {
8678 G.VOM.viewerIsFullscreen = false;
8679 ngscreenfull.exit();
8680 }
8681
8682 G.VOM.$cont.hide(0).off().show(0).html('').remove();
8683 G.VOM.viewerDisplayed = false;
8684
8685 if( vomIdx != null ) {
8686 if( G.GOM.albumIdx == -1 ) {
8687 // album not displayed --> display gallery
8688 DisplayAlbum( '', G.I[G.VOM.items[vomIdx].ngy2ItemIdx].albumID );
8689 }
8690 else {
8691 GalleryResize();
8692 SetLocationHash( G.I[G.VOM.items[vomIdx].ngy2ItemIdx].albumID, '' );
8693 ThumbnailHoverReInitAll();
8694 }
8695 }
8696 G.VOM.timeImgChanged = new Date().getTime();
8697 }
8698 }
8699
8700
8701 // Internal viewer resized -> reposition elements
8702 function ResizeInternalViewer( forceUpdate ) {
8703 forceUpdate = typeof forceUpdate !== 'undefined' ? forceUpdate : false;
8704
8705 if( G.VOM.$toolbar === null ) { return; } // viewer build not finished
8706
8707
8708 // window.requestAnimationFrame( function() { // synchronize with screen
8709 var windowsW = G.VOM.$viewer.width();
8710 var windowsH = G.VOM.$viewer.height();
8711 var $elt = G.VOM.$mediaCurrent.children().eq(0);
8712 if( $elt == null || G.VOM.currItemIdx == -1 ) { return; }
8713
8714 if( !forceUpdate && G.VOM.window.lastWidth == windowsW && G.VOM.window.lastHeight == windowsH ) { return; }
8715
8716 G.VOM.window.lastWidth = windowsW;
8717 G.VOM.window.lastHeight = windowsH;
8718
8719 // var vwImgC_H=$elt.height(),
8720 // vwImgC_W=$elt.width(),
8721 // vwImgC_OHt=$elt.outerHeight(true),
8722 // vwImgC_OHf=$elt.outerHeight(false);
8723
8724 var $tb = G.VOM.$toolbar.find('.toolbar');
8725 var tb_OHt = $tb.outerHeight(true);
8726
8727 switch( G.O.viewerToolbar.position ) {
8728 case 'topOverImage':
8729 G.VOM.$content.css({height: windowsH, width: windowsW, top: 0 });
8730 G.VOM.$toolbar.css({top: 0, bottom: ''});
8731 break;
8732 case 'top':
8733 windowsH -= tb_OHt;
8734 G.VOM.$content.css({height: windowsH, width: windowsW, top: tb_OHt });
8735 G.VOM.$toolbar.css({top: 0});
8736 break;
8737 case 'bottomOverImage':
8738 G.VOM.$content.css({height:windowsH, width: windowsW, bottom: 0, top: 0 });
8739 G.VOM.$toolbar.css({bottom: 0});
8740 break;
8741 case 'bottom':
8742 default:
8743 windowsH -= tb_OHt;
8744 G.VOM.$content.css({ width: windowsW, top: 0, bottom: tb_OHt });
8745 G.VOM.$toolbar.css({bottom: 0});
8746 break;
8747 }
8748
8749 if( !G.VOM.viewerMediaIsChanged && G.VOM.zoom.isZooming ) {
8750 ViewerMediaSetPosAndZoom();
8751 }
8752 else {
8753 G.VOM.zoom.userFactor = 1;
8754 G.VOM.zoom.isZooming = false;
8755 G.VOM.panPosX = 0;
8756 G.VOM.panPosY = 0;
8757 G.VOM.zoom.posX = 0;
8758 G.VOM.zoom.posY = 0;
8759 // G.VOM.$mediaCurrent[0].style[G.CSStransformName] = 'translate3D(0,0,0) ';
8760 // if( G.VOM.NGY2Item(0).mediaKind == 'img' ) {
8761 // G.VOM.$mediaCurrent[0].style[G.CSStransformName] = '';
8762 // }
8763 ViewerMediaSetPosAndZoom();
8764 }
8765 }
8766
8767
8768
8769 /** @function BuildSkeleton */
8770 /** Build the gallery structure **/
8771 function BuildSkeleton() {
8772
8773 // store markup if defined
8774 var $elements=G.$E.base.children('a');
8775 if( $elements.length > 0 ) {
8776 G.O.$markup=$elements;
8777 }
8778 G.$E.base.text('');
8779 G.$E.base.addClass('ngy2_container');
8780
8781 // RTL or LTR
8782 var sRTL='';
8783 if( G.O.RTL ) {
8784 sRTL = 'style="text-align:right;direction:rtl;"';
8785 }
8786
8787 // theme
8788 G.$E.base.addClass(G.O.theme)
8789 // gallery color scheme
8790 SetGalleryTheme();
8791
8792 // Hide icons (thumbnails and breadcrumb)
8793 if( G.O.thumbnailLabel.get('hideIcons') ) {
8794 G.O.icons.thumbnailAlbum = '';
8795 G.O.icons.thumbnailImage = '';
8796 }
8797
8798 // Navigation bar
8799 var styleNavigation="";
8800 if( G.O.navigationFontSize != undefined && G.O.navigationFontSize != '' ) {
8801 styleNavigation=' style="font-size:'+G.O.navigationFontSize+';"';
8802 }
8803 G.$E.conNavigationBar = jQuery('<div class="nGY2Navigationbar" '+styleNavigation+'></div>').appendTo(G.$E.base);
8804
8805 // pre-loader
8806 G.$E.conLoadingB = jQuery('<div class="nanoGalleryLBarOff"><div></div><div></div><div></div><div></div><div></div></div>').appendTo(G.$E.base);
8807
8808 // gallery
8809 G.$E.conTnParent = jQuery('<div class="nGY2Gallery"></div>').appendTo( G.$E.base );
8810 G.$E.conTn = jQuery('<div class="nGY2GallerySub"></div>').appendTo( G.$E.conTnParent );
8811
8812 // configure gallery
8813 switch( G.O.thumbnailAlignment ) {
8814 case 'left':
8815 G.$E.conTnParent.css({'text-align':'left'});
8816 // G.$E.conNavBCon.css({'margin-left':0 });
8817 break;
8818 case 'right':
8819 G.$E.conTnParent.css({'text-align':'right'});
8820 // G.$E.conNavBCon.css({ 'margin-right':0});
8821 break;
8822 }
8823
8824 // apply galleryBuildInit2 css settings to the gallery
8825 if( G.O.galleryBuildInit2 !== undefined ) {
8826 var t1=G.O.galleryBuildInit2.split('|');
8827 for( var i=0; i<t1.length; i++ ) {
8828 var o1=t1[i].split('_');
8829 if( o1.length == 2 ) {
8830 G.$E.conTn.css(o1[0], o1[1]);
8831 }
8832 }
8833 }
8834
8835 // configure gallery depending on some thumbnail hover effects
8836 var effects=G.tn.hoverEffects.std.concat(G.tn.hoverEffects.level1);
8837 for( var j=0; j<effects.length; j++) {
8838 switch( effects[j].type ) {
8839 case 'scale':
8840 case 'rotateZ':
8841 case 'rotateX':
8842 case 'rotateY':
8843 case 'translateX':
8844 case 'translateY':
8845 // handle some special cases
8846 if( effects[j].element == '.nGY2GThumbnail' ) {
8847 // allow thumbnail upscale over the gallery's aera
8848 G.$E.base.css('overflow', 'visible');
8849 G.$E.base.find('.nGY2GallerySub').css('overflow', 'visible');
8850 G.$E.conTnParent.css('overflow', 'visible');
8851 }
8852 break;
8853 }
8854 }
8855
8856 // Gallery bottom container
8857 G.$E.conTnBottom = jQuery('<div class="nGY2GalleryBottom" '+styleNavigation+'></div>').appendTo( G.$E.conTnParent );
8858
8859 // portable edition
8860 if( G.O.portable ) {
8861 // http://www.picresize.com/
8862 // http://base64encode.net/base64-image-encoder
8863 // 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';
8864 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';
8865 G.$E.ngy2i=jQuery('<div class="nGY2PortInfo"><a href="http://nano.gallery" target="_blank" title="nanogallery2 | easy photo gallery for your website" style="font-weight: bold !important;color: #888 !important;font-size: 11px !important;"><img src="'+logo+'" style="height:16px !important;box-shadow: none !important;vertical-align: middle !important;"/> &nbsp; nanogallery2</a></div>').appendTo(G.$E.base);
8866
8867 G.$E.ngy2i.find('a').on({
8868 mouseenter: function () {
8869 jQuery(this).attr('style', 'color: #73A623 !important');
8870 },
8871 mouseleave: function () {
8872 jQuery(this).attr('style', 'color: #888 !important');
8873 }
8874 });
8875 }
8876
8877 // Error console
8878 G.$E.conConsole = jQuery('<div class="nGY2ConsoleParent"></div>').appendTo(G.$E.base);
8879
8880 // i18n translations
8881 i18n();
8882
8883 // cache some thumbnails data (sizes, styles...)
8884 ThumbnailDefCaches();
8885
8886 // do special settings depending for some options
8887 // thumbnail display transition
8888 switch( G.tn.opt.Get('displayTransition') ) {
8889 case 'SCALEDOWN':
8890 case 'RANDOMSCALE':
8891 default:
8892 G.$E.base.css('overflow', 'visible');
8893 G.$E.conTnParent.css('overflow', 'visible');
8894 G.$E.conTn.css('overflow', 'visible');
8895 break;
8896 }
8897
8898 }
8899
8900 function TriggerCustomEvent ( eventName ) {
8901 // G.$E.base.trigger('pageChanged.nanogallery2', new Event('pageChanged.nanogallery2'));
8902 var eN = eventName + '.nanogallery2';
8903 var event=null;
8904 try {
8905 event = new Event( eN );
8906 } catch(e) {
8907 event = document.createEvent('Event');
8908 event.initEvent(eN, false, false);
8909 }
8910 G.$E.base.trigger(eN, event);
8911 }
8912
8913
8914 /** @function SetGlobalEvents */
8915 function SetGlobalEvents() {
8916 // GLOBAL EVENT MANAGEMENT
8917
8918 G.$E.conTnParent.on({
8919 mouseenter: GalleryMouseEnter,
8920 mouseleave: GalleryMouseLeave
8921 }, ".nGY2GThumbnail"); //pass the element as an argument to .on
8922
8923 // G.GOM.hammertime = new NGHammer(G.$E.conTn[0], { touchAction: 'none' });
8924 G.GOM.hammertime = new NGHammer( G.$E.conTn[0] );
8925 // G.GOM.hammertime.domEvents = true;
8926
8927 G.GOM.hammertime.on('pan', function(ev) {
8928 if( !G.VOM.viewerDisplayed ) {
8929 if( G.O.paginationSwipe && G.layout.support.rows && G.galleryDisplayMode.Get() == 'PAGINATION' ) {
8930 G.$E.conTn.css( G.CSStransformName , 'translate('+(ev.deltaX)+'px,0px)');
8931 }
8932 }
8933 });
8934 G.GOM.hammertime.on('panend', function(ev) {
8935 if( !G.VOM.viewerDisplayed ) {
8936 if( G.O.paginationSwipe && G.layout.support.rows && G.galleryDisplayMode.Get() == 'PAGINATION' ) {
8937 if( Math.abs(ev.deltaY) > 100 ) {
8938 // user moved vertically -> cancel pagination
8939 G.$E.conTn.css( G.CSStransformName , 'translate(0px,0px)');
8940 return;
8941 }
8942 if( ev.deltaX > 50 ) {
8943 paginationPreviousPage();
8944 return;
8945 }
8946 if( ev.deltaX < -50 ) {
8947 paginationNextPage();
8948 return;
8949 }
8950 G.$E.conTn.css( G.CSStransformName , 'translate(0px,0px)');
8951 // pX=0;
8952 }
8953 }
8954 });
8955 G.GOM.hammertime.on('tap', function(ev) {
8956 if( !G.VOM.viewerDisplayed ) {
8957 ev.srcEvent.stopPropagation();
8958 ev.srcEvent.preventDefault(); // cancel mouseenter event
8959
8960 if( ev.pointerType == 'mouse') {
8961 if( GalleryClicked(ev.srcEvent) == 'exit' ) { return; }
8962 }
8963 else {
8964 var r = GalleryEventRetrieveElementl(ev.srcEvent, false);
8965 if( r.GOMidx == -1 ) { return; }
8966 if( r.action != 'NONE' && r.action != 'OPEN' ) {
8967 // toolbar touched --> execute action
8968 GalleryClicked(ev.srcEvent);
8969 return;
8970 }
8971
8972 if( G.GOM.slider.hostIdx == r.GOMidx ) {
8973 // touch on thumbnail slider -> open immediately
8974 ThumbnailHoverOutAll();
8975 ThumbnailOpen(G.GOM.items[G.GOM.slider.currentIdx].thumbnailIdx, true);
8976 return;
8977 }
8978
8979 if( G.O.touchAutoOpenDelay > 0 ) {
8980 // one touch scenario
8981 ThumbnailHoverOutAll();
8982 ThumbnailHover( r.GOMidx );
8983 window.clearInterval( G.touchAutoOpenDelayTimerID );
8984 G.touchAutoOpenDelayTimerID = window.setInterval(function(){
8985 window.clearInterval( G.touchAutoOpenDelayTimerID );
8986 ThumbnailOpen( G.GOM.items[r.GOMidx].thumbnailIdx, true );
8987 }, G.O.touchAutoOpenDelay );
8988 }
8989 else {
8990 // two touch scenario
8991 if( !G.I[G.GOM.items[r.GOMidx].thumbnailIdx].hovered ) {
8992 ThumbnailHoverOutAll();
8993 ThumbnailHover(r.GOMidx);
8994 }
8995 else {
8996 // second touch
8997 ThumbnailOpen(G.GOM.items[r.GOMidx].thumbnailIdx, true);
8998 }
8999 }
9000 }
9001 }
9002 });
9003
9004
9005 // browser location hash management
9006 if( G.O.locationHash ) {
9007 // jQuery(window).bind( 'hashchange', function() {
9008 // ProcessLocationHash();
9009 // });
9010 jQuery(window).on('hashchange.nanogallery2.' + G.baseEltID, function() {ProcessLocationHash();} );
9011 }
9012
9013 // Page resize / orientation change
9014 jQuery(window).on('resize.nanogallery2.' + G.baseEltID + ' orientationChange.nanogallery2.' + G.baseEltID, debounce( ResizeWindowEvent, 100, false) );
9015
9016 // Event page scrolled
9017 jQuery(window).on('scroll.nanogallery2.' + G.baseEltID, debounce( OnScrollEvent, 50, false) );
9018
9019 // Debounced function to hide the toolbars on the viewer
9020 G.VOM.toolsHide=debounce( ViewerToolsHide, G.O.viewerHideToolsDelay, false );
9021
9022 // Keyboard management
9023 jQuery(document).keyup(function(e) {
9024 if( G.popup.isDisplayed ) {
9025 switch( e.keyCode) {
9026 case 27: // Esc key
9027 G.popup.close();
9028 break;
9029 }
9030 }
9031 else {
9032 if( G.VOM.viewerDisplayed ) {
9033 ViewerToolsUnHide();
9034 switch( e.keyCode) {
9035 case 27: // Escape key
9036 case 40: // DOWN
9037 CloseInternalViewer(G.VOM.currItemIdx);
9038 break;
9039 case 32: // SPACE
9040 case 13: // ENTER
9041 SlideshowToggle();
9042 break;
9043 case 38: // UP
9044 case 39: // RIGHT
9045 case 33: // PAGE UP
9046 DisplayNextMedia();
9047 break;
9048 case 37: // LEFT
9049 case 34: // PAGE DOWN
9050 DisplayPreviousMedia();
9051 break;
9052 case 35: // END
9053 case 36: // BEGIN
9054 }
9055 }
9056 }
9057 });
9058
9059 // mouse wheel to zoom in/out the image displayed in the internal lightbox
9060 jQuery(window).bind('mousewheel wheel', function(e){
9061 if( G.VOM.viewerDisplayed ) {
9062 var deltaY = 0;
9063 e.preventDefault();
9064
9065 if( ViewerZoomStart() ) {
9066 if (e.originalEvent.deltaY) { // FireFox 17+ (IE9+, Chrome 31+?)
9067 deltaY = e.originalEvent.deltaY;
9068 } else if (e.originalEvent.wheelDelta) {
9069 deltaY = -e.originalEvent.wheelDelta;
9070 }
9071 ViewerZoomIn( deltaY <= 0 ? true : false );
9072 }
9073 }
9074 });
9075
9076 // mouse mouse -> unhide lightbox toolbars
9077 jQuery(window).bind('mousemove', function(e){
9078 if( G.VOM.viewerDisplayed ) {
9079 debounce( ViewerToolsUnHide, 400, false )();
9080 }
9081 });
9082
9083 // fullscreen mode on/off --> internal lightbox
9084 if( ngscreenfull.enabled ) {
9085 // ngscreenfull.onchange(() => {
9086 ngscreenfull.onchange( function() {
9087 if( G.VOM.viewerDisplayed ) {
9088 if( ngscreenfull.isFullscreen ) {
9089 G.VOM.viewerIsFullscreen=true;
9090 G.VOM.$viewer.find('.fullscreenButton').html(G.O.icons.viewerFullscreenOff);
9091 }
9092 else {
9093 G.VOM.viewerIsFullscreen=false;
9094 G.VOM.$viewer.find('.fullscreenButton').html(G.O.icons.viewerFullscreenOn);
9095 }
9096 }
9097 });
9098 }
9099
9100 }
9101
9102 //----- Manage browser location hash (deep linking and browser back/forward)
9103 function ProcessLocationHash() {
9104
9105 // standard use case -> location hash processing
9106 if( !G.O.locationHash ) { return false; }
9107
9108 var curGal='#nanogallery/'+G.baseEltID+'/',
9109 newLocationHash=location.hash;
9110 if( G.O.debugMode ) {
9111 console.log('------------------------ PROCESS LOCATION HASH');
9112 console.log('newLocationHash1: ' +newLocationHash);
9113 console.log('G.locationHashLastUsed: ' +G.locationHashLastUsed);
9114 }
9115
9116 if( newLocationHash == '' ) {
9117 // if( G.GOM.lastDisplayedIdx != -1 ) {
9118 if( G.locationHashLastUsed !== '' ) {
9119 // back button and no hash --> display first album
9120 if( G.O.debugMode ) { console.log('display root album' ); }
9121 G.locationHashLastUsed='';
9122 if( G.O.debugMode ) { console.log('new3 G.locationHashLastUsed: '+G.locationHashLastUsed); }
9123 DisplayAlbum( '', '0');
9124 return true;
9125 }
9126 }
9127
9128 if( newLocationHash == G.locationHashLastUsed ) { return; }
9129
9130 if( newLocationHash.indexOf(curGal) == 0 ) {
9131 // item IDs detected
9132 var IDs=parseIDs( newLocationHash.substring(curGal.length) );
9133 if( IDs.imageID != '0' ) {
9134 if( G.O.debugMode ) { console.log('display image: ' + IDs.albumID +'-'+ IDs.imageID ); }
9135 DisplayPhoto( IDs.imageID, IDs.albumID );
9136 return true;
9137 }
9138 else {
9139 if( G.O.debugMode ) { console.log('display album: ' + IDs.albumID ); }
9140 DisplayAlbum( '-1', IDs.albumID );
9141 return true;
9142 }
9143 }
9144
9145 return false;
9146 }
9147
9148 //---- Set a new browser location hash
9149 function SetLocationHash(albumID, imageID ) {
9150 if( !G.O.locationHash ) { return false; }
9151
9152 if( G.O.debugMode ) {
9153 console.log('------------------------ SET LOCATION HASH');
9154 }
9155
9156 if( imageID == '' && (albumID == '-1' || albumID == '0' || G.O.album == albumID ) ) {
9157 // root album level --> do not set top.location.hash if not already set
9158 if( location.hash != '' ) {
9159 // try to clear the hash if set
9160 if ("pushState" in history) {
9161 history.pushState("", document.title, window.location.pathname + window.location.search);
9162 }
9163 else {
9164 location.hash='';
9165 }
9166 }
9167 G.locationHashLastUsed='';
9168 if( G.O.debugMode ) { console.log('new2 G.locationHashLastUsed: '+G.locationHashLastUsed); }
9169 return;
9170 }
9171
9172 var newLocationHash='#'+'nanogallery/'+G.baseEltID+'/'+ albumID;
9173 if( imageID != '' ) {
9174 newLocationHash+='/'+imageID;
9175 }
9176
9177 var lH=location.hash;
9178 if( G.O.debugMode ) {
9179 console.log('newLocationHash2: '+newLocationHash);
9180 console.log('location.hash: '+lH);
9181 }
9182
9183 G.locationHashLastUsed=newLocationHash;
9184 if( G.O.debugMode ) { console.log('new G.locationHashLastUsed: '+G.locationHashLastUsed); }
9185
9186 if( lH == '' || lH != newLocationHash ) {
9187 // G.locationHashLastUsed='#'+newLocationHash;
9188 try {
9189 top.location.hash=newLocationHash;
9190 }
9191 catch(e) {
9192 // location hash is not supported by current browser --> disable the option
9193 G.O.locationHash=false;
9194 }
9195 }
9196 }
9197
9198
9199 function ResizeWindowEvent() {
9200 G.GOM.cache.viewport=getViewport();
9201 G.GOM.cache.areaWidth=G.$E.conTnParent.width();
9202 G.GOM.cache.containerOffset=G.$E.conTnParent.offset();
9203
9204 if( G.VOM.viewerDisplayed ) {
9205 ResizeInternalViewer();
9206 }
9207 else {
9208 if( G.galleryResizeEventEnabled ) {
9209 var nw = RetrieveCurWidth();
9210 if( G.GOM.albumIdx != -1 &&
9211 ( G.tn.settings.height[G.GOM.curNavLevel][G.GOM.curWidth] != G.tn.settings.height[G.GOM.curNavLevel][nw] ||
9212 G.tn.settings.width[G.GOM.curNavLevel][G.GOM.curWidth] != G.tn.settings.width[G.GOM.curNavLevel][nw] ) ) {
9213 // do not use settings.getH() / settings.getW()
9214 // thumbnail size changed --> render the gallery with the new sizes
9215 G.GOM.curWidth = nw;
9216 //G.layout.SetEngine();
9217 G.GOM.pagination.currentPage = 0;
9218 GalleryRender( G.GOM.albumIdx );
9219 }
9220 else {
9221 GalleryResize();
9222 }
9223 }
9224 }
9225 }
9226
9227
9228 function OnScrollEvent() {
9229 // if( G.scrollTimeOut ) {
9230 // clearTimeout(G.scrollTimeOut);
9231 // }
9232
9233 // G.scrollTimeOut = setTimeout(function () {
9234 if( !G.VOM.viewerDisplayed ) {
9235 if( G.galleryResizeEventEnabled ) {
9236 GalleryResize();
9237 }
9238 return;
9239 }
9240 // }, 100);
9241 }
9242
9243
9244 // I18N : define text translations
9245 function i18n() {
9246
9247 // browser language
9248 G.i18nLang = (navigator.language || navigator.userLanguage).toUpperCase();
9249 if( G.i18nLang === 'UNDEFINED') { G.i18nLang=''; }
9250
9251 var llang=-('_'+G.i18nLang).length;
9252
9253 if( toType(G.O.i18n) == 'object' ){
9254
9255 for( var key in G.O.i18n ) {
9256 //var value = G.O.i18n[key];
9257 var s=key.substr(llang);
9258 if( s == ('_'+G.i18nLang) ) {
9259 G.i18nTranslations[key.substr(0,key.length-s.length)]=G.O.i18n[key];
9260 }
9261 else {
9262 G.i18nTranslations[key]=G.O.i18n[key];
9263 }
9264 }
9265 }
9266 }
9267
9268 function GetI18nItem( item, property ) {
9269 var s='';
9270 if( G.i18nLang != '' ) {
9271 if( item[property+'_'+G.i18nLang] !== undefined && item[property+'_'+G.i18nLang].length>0 ) {
9272 s=item[property+'_'+G.i18nLang];
9273 return s;
9274 }
9275 }
9276 s=item[property];
9277 return s;
9278 }
9279
9280
9281 function RetrieveCurWidth() {
9282 var vpW= G.GOM.cache.viewport.w;
9283
9284 if( G.O.breakpointSizeSM > 0 && vpW < G.O.breakpointSizeSM) { return 'xs'; }
9285 if( G.O.breakpointSizeME > 0 && vpW < G.O.breakpointSizeME) { return 'sm'; }
9286 if( G.O.breakpointSizeLA > 0 && vpW < G.O.breakpointSizeLA) { return 'me'; }
9287 if( G.O.breakpointSizeXL > 0 && vpW < G.O.breakpointSizeXL) { return 'la'; }
9288
9289 return 'xl';
9290 }
9291
9292
9293 /** @function browserNotification */
9294 function browserNotification() {
9295 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>';
9296 m += '&nbsp;&nbsp;&nbsp; <a href="http://www.google.com/chrome/?hl=en-US)">Chrome</a><br>';
9297 m += '&nbsp;&nbsp;&nbsp; <a href="http://www.mozilla.com/firefox/)">Firefox</a><br>';
9298 m += '&nbsp;&nbsp;&nbsp; <a href="http://www.microsoft.com/windows/internet-explorer/default.aspx">Internet Explorer</a><br>';
9299 m += '&nbsp;&nbsp;&nbsp; <a href="http://www.apple.com/safari/download/">Safari</a>';
9300 NanoAlert(G, m, false);
9301 }
9302
9303 // 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
9304 function FirstSupportedPropertyName(prefixedPropertyNames) {
9305 var tempDiv = document.createElement("div");
9306 for (var i = 0; i < prefixedPropertyNames.length; ++i) {
9307 if (typeof tempDiv.style[prefixedPropertyNames[i]] != 'undefined')
9308 return prefixedPropertyNames[i];
9309 }
9310 return null;
9311 }
9312
9313
9314
9315
9316 }
9317
9318
9319
9320//##########################################################################################################################
9321//## imagesLoaded ##########################################################################################################
9322//##########################################################################################################################
9323
9324// external module EMBEDED in nanogallery
9325// NGY BUILD:
9326// replace "imagesLoaded" with "ngimagesLoaded"
9327// replace "ImagesLoaded" with "ngImagesLoaded"
9328// replace "EvEmitter" with "ngEvEmitter"
9329// replace "var $ = window.jQuery" with "var $ = jQuery;"
9330// 2x (global.ngEvEmitter and window.ngimagesLoaded = f...)ignore package manager and set browser global
9331
9332
9333
9334/*!
9335 * imagesLoaded PACKAGED v4.1.1
9336 * JavaScript is all like "You images are done yet or what?"
9337 * MIT License
9338 */
9339
9340/**
9341 * EvEmitter v1.0.3
9342 * Lil' event emitter
9343 * MIT License
9344 */
9345
9346
9347/* jshint unused: true, undef: true, strict: true */
9348
9349( function( global, factory ) {
9350 // universal module definition
9351 /* jshint strict: false */ /* globals define, module, window */
9352// if ( typeof define == 'function' && define.amd ) {
9353 // AMD - RequireJS
9354// define( 'ev-emitter/ev-emitter',factory );
9355// } else if ( typeof module == 'object' && module.exports ) {
9356 // CommonJS - Browserify, Webpack
9357// module.exports = factory();
9358// } else {
9359 // Browser globals
9360 global.ngEvEmitter = factory();
9361// }
9362
9363}( typeof window != 'undefined' ? window : this, function() {
9364
9365
9366
9367function ngEvEmitter() {}
9368
9369var proto = ngEvEmitter.prototype;
9370
9371proto.on = function( eventName, listener ) {
9372 if ( !eventName || !listener ) {
9373 return;
9374 }
9375 // set events hash
9376 var events = this._events = this._events || {};
9377 // set listeners array
9378 var listeners = events[ eventName ] = events[ eventName ] || [];
9379 // only add once
9380 if ( listeners.indexOf( listener ) == -1 ) {
9381 listeners.push( listener );
9382 }
9383
9384 return this;
9385};
9386
9387proto.once = function( eventName, listener ) {
9388 if ( !eventName || !listener ) {
9389 return;
9390 }
9391 // add event
9392 this.on( eventName, listener );
9393 // set once flag
9394 // set onceEvents hash
9395 var onceEvents = this._onceEvents = this._onceEvents || {};
9396 // set onceListeners object
9397 var onceListeners = onceEvents[ eventName ] = onceEvents[ eventName ] || {};
9398 // set flag
9399 onceListeners[ listener ] = true;
9400
9401 return this;
9402};
9403
9404proto.off = function( eventName, listener ) {
9405 var listeners = this._events && this._events[ eventName ];
9406 if ( !listeners || !listeners.length ) {
9407 return;
9408 }
9409 var index = listeners.indexOf( listener );
9410 if ( index != -1 ) {
9411 listeners.splice( index, 1 );
9412 }
9413
9414 return this;
9415};
9416
9417proto.emitEvent = function( eventName, args ) {
9418 var listeners = this._events && this._events[ eventName ];
9419 if ( !listeners || !listeners.length ) {
9420 return;
9421 }
9422 var i = 0;
9423 var listener = listeners[i];
9424 args = args || [];
9425 // once stuff
9426 var onceListeners = this._onceEvents && this._onceEvents[ eventName ];
9427
9428 while ( listener ) {
9429 var isOnce = onceListeners && onceListeners[ listener ];
9430 if ( isOnce ) {
9431 // remove listener
9432 // remove before trigger to prevent recursion
9433 this.off( eventName, listener );
9434 // unset once flag
9435 delete onceListeners[ listener ];
9436 }
9437 // trigger listener
9438 listener.apply( this, args );
9439 // get next listener
9440 i += isOnce ? 0 : 1;
9441 listener = listeners[i];
9442 }
9443
9444 return this;
9445};
9446
9447return ngEvEmitter;
9448
9449}));
9450
9451/*!
9452 * ngimagesLoaded v4.1.1
9453 * JavaScript is all like "You images are done yet or what?"
9454 * MIT License
9455 */
9456
9457( function( window, factory ) { 'use strict';
9458 // universal module definition
9459
9460 /*global define: false, module: false, require: false */
9461
9462// if ( typeof define == 'function' && define.amd ) {
9463 // AMD
9464// define( [
9465// 'ev-emitter/ev-emitter'
9466// ], function( ngEvEmitter ) {
9467// return factory( window, ngEvEmitter );
9468// });
9469// } else if ( typeof module == 'object' && module.exports ) {
9470 // CommonJS
9471// module.exports = factory(
9472// window,
9473// require('ev-emitter')
9474// );
9475// } else {
9476 // browser global
9477 window.ngimagesLoaded = factory(
9478 window,
9479 window.ngEvEmitter
9480 );
9481 //}
9482
9483})( window,
9484
9485// -------------------------- factory -------------------------- //
9486
9487function factory( window, ngEvEmitter ) {
9488
9489
9490
9491// var $ = window.jQuery;
9492var $ = jQuery;
9493var console = window.console;
9494
9495// -------------------------- helpers -------------------------- //
9496
9497// extend objects
9498function extend( a, b ) {
9499 for ( var prop in b ) {
9500 a[ prop ] = b[ prop ];
9501 }
9502 return a;
9503}
9504
9505// turn element or nodeList into an array
9506function makeArray( obj ) {
9507 var ary = [];
9508 if ( Array.isArray( obj ) ) {
9509 // use object if already an array
9510 ary = obj;
9511 } else if ( typeof obj.length == 'number' ) {
9512 // convert nodeList to array
9513 for ( var i=0; i < obj.length; i++ ) {
9514 ary.push( obj[i] );
9515 }
9516 } else {
9517 // array of single index
9518 ary.push( obj );
9519 }
9520 return ary;
9521}
9522
9523// -------------------------- ngimagesLoaded -------------------------- //
9524
9525/**
9526 * @param {Array, Element, NodeList, String} elem
9527 * @param {Object or Function} options - if function, use as callback
9528 * @param {Function} onAlways - callback function
9529 */
9530function ngImagesLoaded( elem, options, onAlways ) {
9531 // coerce ngImagesLoaded() without new, to be new ngImagesLoaded()
9532 if ( !( this instanceof ngImagesLoaded ) ) {
9533 return new ngImagesLoaded( elem, options, onAlways );
9534 }
9535 // use elem as selector string
9536 if ( typeof elem == 'string' ) {
9537 elem = document.querySelectorAll( elem );
9538 }
9539
9540 this.elements = makeArray( elem );
9541 this.options = extend( {}, this.options );
9542
9543 if ( typeof options == 'function' ) {
9544 onAlways = options;
9545 } else {
9546 extend( this.options, options );
9547 }
9548
9549 if ( onAlways ) {
9550 this.on( 'always', onAlways );
9551 }
9552
9553 this.getImages();
9554
9555 if ( $ ) {
9556 // add jQuery Deferred object
9557 this.jqDeferred = new $.Deferred();
9558 }
9559
9560 // HACK check async to allow time to bind listeners
9561 setTimeout( function() {
9562 this.check();
9563 }.bind( this ));
9564}
9565
9566ngImagesLoaded.prototype = Object.create( ngEvEmitter.prototype );
9567
9568ngImagesLoaded.prototype.options = {};
9569
9570ngImagesLoaded.prototype.getImages = function() {
9571 this.images = [];
9572
9573 // filter & find items if we have an item selector
9574 this.elements.forEach( this.addElementImages, this );
9575};
9576
9577/**
9578 * @param {Node} element
9579 */
9580ngImagesLoaded.prototype.addElementImages = function( elem ) {
9581 // filter siblings
9582 if ( elem.nodeName == 'IMG' ) {
9583 this.addImage( elem );
9584 }
9585 // get background image on element
9586 if ( this.options.background === true ) {
9587 this.addElementBackgroundImages( elem );
9588 }
9589
9590 // find children
9591 // no non-element nodes, #143
9592 var nodeType = elem.nodeType;
9593 if ( !nodeType || !elementNodeTypes[ nodeType ] ) {
9594 return;
9595 }
9596 var childImgs = elem.querySelectorAll('img');
9597 // concat childElems to filterFound array
9598 for ( var i=0; i < childImgs.length; i++ ) {
9599 var img = childImgs[i];
9600 this.addImage( img );
9601 }
9602
9603 // get child background images
9604 if ( typeof this.options.background == 'string' ) {
9605 var children = elem.querySelectorAll( this.options.background );
9606 for ( i=0; i < children.length; i++ ) {
9607 var child = children[i];
9608 this.addElementBackgroundImages( child );
9609 }
9610 }
9611};
9612
9613var elementNodeTypes = {
9614 1: true,
9615 9: true,
9616 11: true
9617};
9618
9619ngImagesLoaded.prototype.addElementBackgroundImages = function( elem ) {
9620 var style = getComputedStyle( elem );
9621 if ( !style ) {
9622 // Firefox returns null if in a hidden iframe https://bugzil.la/548397
9623 return;
9624 }
9625 // get url inside url("...")
9626 var reURL = /url\((['"])?(.*?)\1\)/gi;
9627 var matches = reURL.exec( style.backgroundImage );
9628 while ( matches !== null ) {
9629 var url = matches && matches[2];
9630 if ( url ) {
9631 this.addBackground( url, elem );
9632 }
9633 matches = reURL.exec( style.backgroundImage );
9634 }
9635};
9636
9637/**
9638 * @param {Image} img
9639 */
9640ngImagesLoaded.prototype.addImage = function( img ) {
9641 var loadingImage = new LoadingImage( img );
9642 this.images.push( loadingImage );
9643};
9644
9645ngImagesLoaded.prototype.addBackground = function( url, elem ) {
9646 var background = new Background( url, elem );
9647 this.images.push( background );
9648};
9649
9650ngImagesLoaded.prototype.check = function() {
9651 var _this = this;
9652 this.progressedCount = 0;
9653 this.hasAnyBroken = false;
9654 // complete if no images
9655 if ( !this.images.length ) {
9656 this.complete();
9657 return;
9658 }
9659
9660 function onProgress( image, elem, message ) {
9661 // HACK - Chrome triggers event before object properties have changed. #83
9662 setTimeout( function() {
9663 _this.progress( image, elem, message );
9664 });
9665 }
9666
9667 this.images.forEach( function( loadingImage ) {
9668 loadingImage.once( 'progress', onProgress );
9669 loadingImage.check();
9670 });
9671};
9672
9673ngImagesLoaded.prototype.progress = function( image, elem, message ) {
9674 this.progressedCount++;
9675 this.hasAnyBroken = this.hasAnyBroken || !image.isLoaded;
9676 // progress event
9677 this.emitEvent( 'progress', [ this, image, elem ] );
9678 if ( this.jqDeferred && this.jqDeferred.notify ) {
9679 this.jqDeferred.notify( this, image );
9680 }
9681 // check if completed
9682 if ( this.progressedCount == this.images.length ) {
9683 this.complete();
9684 }
9685
9686 if ( this.options.debug && console ) {
9687 console.log( 'progress: ' + message, image, elem );
9688 }
9689};
9690
9691ngImagesLoaded.prototype.complete = function() {
9692 var eventName = this.hasAnyBroken ? 'fail' : 'done';
9693 this.isComplete = true;
9694 this.emitEvent( eventName, [ this ] );
9695 this.emitEvent( 'always', [ this ] );
9696 if ( this.jqDeferred ) {
9697 var jqMethod = this.hasAnyBroken ? 'reject' : 'resolve';
9698 this.jqDeferred[ jqMethod ]( this );
9699 }
9700};
9701
9702// -------------------------- -------------------------- //
9703
9704function LoadingImage( img ) {
9705 this.img = img;
9706}
9707
9708LoadingImage.prototype = Object.create( ngEvEmitter.prototype );
9709
9710LoadingImage.prototype.check = function() {
9711 // If complete is true and browser supports natural sizes,
9712 // try to check for image status manually.
9713 var isComplete = this.getIsImageComplete();
9714 if ( isComplete ) {
9715 // report based on naturalWidth
9716 this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' );
9717 return;
9718 }
9719
9720 // If none of the checks above matched, simulate loading on detached element.
9721 this.proxyImage = new Image();
9722 this.proxyImage.addEventListener( 'load', this );
9723 this.proxyImage.addEventListener( 'error', this );
9724 // bind to image as well for Firefox. #191
9725 this.img.addEventListener( 'load', this );
9726 this.img.addEventListener( 'error', this );
9727 this.proxyImage.src = this.img.src;
9728};
9729
9730LoadingImage.prototype.getIsImageComplete = function() {
9731 return this.img.complete && this.img.naturalWidth !== undefined;
9732};
9733
9734LoadingImage.prototype.confirm = function( isLoaded, message ) {
9735 this.isLoaded = isLoaded;
9736 this.emitEvent( 'progress', [ this, this.img, message ] );
9737};
9738
9739// ----- events ----- //
9740
9741// trigger specified handler for event type
9742LoadingImage.prototype.handleEvent = function( event ) {
9743 var method = 'on' + event.type;
9744 if ( this[ method ] ) {
9745 this[ method ]( event );
9746 }
9747};
9748
9749LoadingImage.prototype.onload = function() {
9750 this.confirm( true, 'onload' );
9751 this.unbindEvents();
9752};
9753
9754LoadingImage.prototype.onerror = function() {
9755 this.confirm( false, 'onerror' );
9756 this.unbindEvents();
9757};
9758
9759LoadingImage.prototype.unbindEvents = function() {
9760 this.proxyImage.removeEventListener( 'load', this );
9761 this.proxyImage.removeEventListener( 'error', this );
9762 this.img.removeEventListener( 'load', this );
9763 this.img.removeEventListener( 'error', this );
9764};
9765
9766// -------------------------- Background -------------------------- //
9767
9768function Background( url, element ) {
9769 this.url = url;
9770 this.element = element;
9771 this.img = new Image();
9772}
9773
9774// inherit LoadingImage prototype
9775Background.prototype = Object.create( LoadingImage.prototype );
9776
9777Background.prototype.check = function() {
9778 this.img.addEventListener( 'load', this );
9779 this.img.addEventListener( 'error', this );
9780 this.img.src = this.url;
9781 // check if image is already complete
9782 var isComplete = this.getIsImageComplete();
9783 if ( isComplete ) {
9784 this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' );
9785 this.unbindEvents();
9786 }
9787};
9788
9789Background.prototype.unbindEvents = function() {
9790 this.img.removeEventListener( 'load', this );
9791 this.img.removeEventListener( 'error', this );
9792};
9793
9794Background.prototype.confirm = function( isLoaded, message ) {
9795 this.isLoaded = isLoaded;
9796 this.emitEvent( 'progress', [ this, this.element, message ] );
9797};
9798
9799// -------------------------- jQuery -------------------------- //
9800
9801ngImagesLoaded.makeJQueryPlugin = function( jQuery ) {
9802 jQuery = jQuery || window.jQuery;
9803 if ( !jQuery ) {
9804 return;
9805 }
9806 // set local variable
9807 $ = jQuery;
9808 // $().ngimagesLoaded()
9809 $.fn.ngimagesLoaded = function( options, callback ) {
9810 var instance = new ngImagesLoaded( this, options, callback );
9811 return instance.jqDeferred.promise( $(this) );
9812 };
9813};
9814// try making plugin
9815ngImagesLoaded.makeJQueryPlugin();
9816
9817// -------------------------- -------------------------- //
9818
9819return ngImagesLoaded;
9820
9821});
9822
9823
9824
9825//##########################################################################################################################
9826//## screenfull.js #########################################################################################################
9827//##########################################################################################################################
9828
9829// screenfull.js
9830// v3.2.0
9831// by sindresorhus - https://github.com/sindresorhus
9832// from: https://github.com/sindresorhus/screenfull.js
9833
9834// external module embeded in nanogallery
9835// NGY BUILD:
9836// replace "screenfull" with "ngscreenfull"
9837//
9838
9839(function () {
9840 'use strict';
9841
9842 var document = typeof window === 'undefined' ? {} : window.document;
9843 var isCommonjs = typeof module !== 'undefined' && module.exports;
9844 var keyboardAllowed = typeof Element !== 'undefined' && 'ALLOW_KEYBOARD_INPUT' in Element;
9845
9846 var fn = (function () {
9847 var val;
9848
9849 var fnMap = [
9850 [
9851 'requestFullscreen',
9852 'exitFullscreen',
9853 'fullscreenElement',
9854 'fullscreenEnabled',
9855 'fullscreenchange',
9856 'fullscreenerror'
9857 ],
9858 // New WebKit
9859 [
9860 'webkitRequestFullscreen',
9861 'webkitExitFullscreen',
9862 'webkitFullscreenElement',
9863 'webkitFullscreenEnabled',
9864 'webkitfullscreenchange',
9865 'webkitfullscreenerror'
9866
9867 ],
9868 // Old WebKit (Safari 5.1)
9869 [
9870 'webkitRequestFullScreen',
9871 'webkitCancelFullScreen',
9872 'webkitCurrentFullScreenElement',
9873 'webkitCancelFullScreen',
9874 'webkitfullscreenchange',
9875 'webkitfullscreenerror'
9876
9877 ],
9878 [
9879 'mozRequestFullScreen',
9880 'mozCancelFullScreen',
9881 'mozFullScreenElement',
9882 'mozFullScreenEnabled',
9883 'mozfullscreenchange',
9884 'mozfullscreenerror'
9885 ],
9886 [
9887 'msRequestFullscreen',
9888 'msExitFullscreen',
9889 'msFullscreenElement',
9890 'msFullscreenEnabled',
9891 'MSFullscreenChange',
9892 'MSFullscreenError'
9893 ]
9894 ];
9895
9896 var i = 0;
9897 var l = fnMap.length;
9898 var ret = {};
9899
9900 for (; i < l; i++) {
9901 val = fnMap[i];
9902 if (val && val[1] in document) {
9903 for (i = 0; i < val.length; i++) {
9904 ret[fnMap[0][i]] = val[i];
9905 }
9906 return ret;
9907 }
9908 }
9909
9910 return false;
9911 })();
9912
9913 var ngscreenfull = {
9914 request: function (elem) {
9915 var request = fn.requestFullscreen;
9916
9917 elem = elem || document.documentElement;
9918
9919 // Work around Safari 5.1 bug: reports support for
9920 // keyboard in fullscreen even though it doesn't.
9921 // Browser sniffing, since the alternative with
9922 // setTimeout is even worse.
9923 if (/5\.1[.\d]* Safari/.test(navigator.userAgent)) {
9924 elem[request]();
9925 } else {
9926 elem[request](keyboardAllowed && Element.ALLOW_KEYBOARD_INPUT);
9927 }
9928 },
9929 exit: function () {
9930 document[fn.exitFullscreen]();
9931 },
9932 toggle: function (elem) {
9933 if (this.isFullscreen) {
9934 this.exit();
9935 } else {
9936 this.request(elem);
9937 }
9938 },
9939 onchange: function (callback) {
9940 document.addEventListener(fn.fullscreenchange, callback, false);
9941 },
9942 onerror: function (callback) {
9943 document.addEventListener(fn.fullscreenerror, callback, false);
9944 },
9945 raw: fn
9946 };
9947
9948 if (!fn) {
9949 if (isCommonjs) {
9950 module.exports = false;
9951 } else {
9952 window.ngscreenfull = false;
9953 }
9954
9955 return;
9956 }
9957
9958 Object.defineProperties(ngscreenfull, {
9959 isFullscreen: {
9960 get: function () {
9961 return Boolean(document[fn.fullscreenElement]);
9962 }
9963 },
9964 element: {
9965 enumerable: true,
9966 get: function () {
9967 return document[fn.fullscreenElement];
9968 }
9969 },
9970 enabled: {
9971 enumerable: true,
9972 get: function () {
9973 // Coerce to boolean in case of old WebKit
9974 return Boolean(document[fn.fullscreenEnabled]);
9975 }
9976 }
9977 });
9978
9979 if (isCommonjs) {
9980 module.exports = ngscreenfull;
9981 } else {
9982 window.ngscreenfull = ngscreenfull;
9983 }
9984})();
9985
9986
9987
9988//##########################################################################################################################
9989//## Shifty ################################################################################################################
9990//##########################################################################################################################
9991
9992 /*!
9993 * Shifty
9994 * By Jeremy Kahn - jeremyckahn@gmail.com
9995 */
9996
9997// external module EMBEDED in nanogallery
9998// NGY BUILD:
9999//
10000// replace "Tweenable" with "NGTweenable"
10001// replace "define.amd" with "define.amdDISABLED"
10002/* shifty - v1.5.3 - 2016-11-29 - http://jeremyckahn.github.io/shifty */
10003;(function () {
10004 var root = this || Function('return this')();
10005
10006/**
10007 * Shifty Core
10008 * By Jeremy Kahn - jeremyckahn@gmail.com
10009 */
10010
10011var NGTweenable = (function () {
10012
10013 'use strict';
10014
10015 // Aliases that get defined later in this function
10016 var formula;
10017
10018 // CONSTANTS
10019 var DEFAULT_SCHEDULE_FUNCTION;
10020 var DEFAULT_EASING = 'linear';
10021 var DEFAULT_DURATION = 500;
10022 var UPDATE_TIME = 1000 / 60;
10023
10024 var _now = Date.now
10025 ? Date.now
10026 : function () {return +new Date();};
10027
10028 var now = typeof SHIFTY_DEBUG_NOW !== 'undefined' ? SHIFTY_DEBUG_NOW : _now;
10029
10030 if (typeof window !== 'undefined') {
10031 // requestAnimationFrame() shim by Paul Irish (modified for Shifty)
10032 // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
10033 DEFAULT_SCHEDULE_FUNCTION = window.requestAnimationFrame
10034 || window.webkitRequestAnimationFrame
10035 || window.oRequestAnimationFrame
10036 || window.msRequestAnimationFrame
10037 || (window.mozCancelRequestAnimationFrame
10038 && window.mozRequestAnimationFrame)
10039 || setTimeout;
10040 } else {
10041 DEFAULT_SCHEDULE_FUNCTION = setTimeout;
10042 }
10043
10044 function noop () {
10045 // NOOP!
10046 }
10047
10048 /**
10049 * Handy shortcut for doing a for-in loop. This is not a "normal" each
10050 * function, it is optimized for Shifty. The iterator function only receives
10051 * the property name, not the value.
10052 * @param {Object} obj
10053 * @param {Function(string)} fn
10054 * @private
10055 */
10056 function each (obj, fn) {
10057 var key;
10058 for (key in obj) {
10059 if (Object.hasOwnProperty.call(obj, key)) {
10060 fn(key);
10061 }
10062 }
10063 }
10064
10065 /**
10066 * Perform a shallow copy of Object properties.
10067 * @param {Object} targetObject The object to copy into
10068 * @param {Object} srcObject The object to copy from
10069 * @return {Object} A reference to the augmented `targetObj` Object
10070 * @private
10071 */
10072 function shallowCopy (targetObj, srcObj) {
10073 each(srcObj, function (prop) {
10074 targetObj[prop] = srcObj[prop];
10075 });
10076
10077 return targetObj;
10078 }
10079
10080 /**
10081 * Copies each property from src onto target, but only if the property to
10082 * copy to target is undefined.
10083 * @param {Object} target Missing properties in this Object are filled in
10084 * @param {Object} src
10085 * @private
10086 */
10087 function defaults (target, src) {
10088 each(src, function (prop) {
10089 if (typeof target[prop] === 'undefined') {
10090 target[prop] = src[prop];
10091 }
10092 });
10093 }
10094
10095 /**
10096 * Calculates the interpolated tween values of an Object for a given
10097 * timestamp.
10098 * @param {Number} forPosition The position to compute the state for.
10099 * @param {Object} currentState Current state properties.
10100 * @param {Object} originalState: The original state properties the Object is
10101 * tweening from.
10102 * @param {Object} targetState: The destination state properties the Object
10103 * is tweening to.
10104 * @param {number} duration: The length of the tween in milliseconds.
10105 * @param {number} timestamp: The UNIX epoch time at which the tween began.
10106 * @param {Object} easing: This Object's keys must correspond to the keys in
10107 * targetState.
10108 * @private
10109 */
10110 function tweenProps (forPosition, currentState, originalState, targetState,
10111 duration, timestamp, easing) {
10112 var normalizedPosition =
10113 forPosition < timestamp ? 0 : (forPosition - timestamp) / duration;
10114
10115
10116 var prop;
10117 var easingObjectProp;
10118 var easingFn;
10119 for (prop in currentState) {
10120 if (currentState.hasOwnProperty(prop)) {
10121 easingObjectProp = easing[prop];
10122 easingFn = typeof easingObjectProp === 'function'
10123 ? easingObjectProp
10124 : formula[easingObjectProp];
10125
10126 currentState[prop] = tweenProp(
10127 originalState[prop],
10128 targetState[prop],
10129 easingFn,
10130 normalizedPosition
10131 );
10132 }
10133 }
10134
10135 return currentState;
10136 }
10137
10138 /**
10139 * Tweens a single property.
10140 * @param {number} start The value that the tween started from.
10141 * @param {number} end The value that the tween should end at.
10142 * @param {Function} easingFunc The easing curve to apply to the tween.
10143 * @param {number} position The normalized position (between 0.0 and 1.0) to
10144 * calculate the midpoint of 'start' and 'end' against.
10145 * @return {number} The tweened value.
10146 * @private
10147 */
10148 function tweenProp (start, end, easingFunc, position) {
10149 return start + (end - start) * easingFunc(position);
10150 }
10151
10152 /**
10153 * Applies a filter to NGTweenable instance.
10154 * @param {NGTweenable} tweenable The `NGTweenable` instance to call the filter
10155 * upon.
10156 * @param {String} filterName The name of the filter to apply.
10157 * @private
10158 */
10159 function applyFilter (tweenable, filterName) {
10160 var filters = NGTweenable.prototype.filter;
10161 var args = tweenable._filterArgs;
10162
10163 each(filters, function (name) {
10164 if (typeof filters[name][filterName] !== 'undefined') {
10165 filters[name][filterName].apply(tweenable, args);
10166 }
10167 });
10168 }
10169
10170 var timeoutHandler_endTime;
10171 var timeoutHandler_currentTime;
10172 var timeoutHandler_isEnded;
10173 var timeoutHandler_offset;
10174 /**
10175 * Handles the update logic for one step of a tween.
10176 * @param {NGTweenable} tweenable
10177 * @param {number} timestamp
10178 * @param {number} delay
10179 * @param {number} duration
10180 * @param {Object} currentState
10181 * @param {Object} originalState
10182 * @param {Object} targetState
10183 * @param {Object} easing
10184 * @param {Function(Object, *, number)} step
10185 * @param {Function(Function,number)}} schedule
10186 * @param {number=} opt_currentTimeOverride Needed for accurate timestamp in
10187 * NGTweenable#seek.
10188 * @private
10189 */
10190 function timeoutHandler (tweenable, timestamp, delay, duration, currentState,
10191 originalState, targetState, easing, step, schedule,
10192 opt_currentTimeOverride) {
10193
10194 timeoutHandler_endTime = timestamp + delay + duration;
10195
10196 timeoutHandler_currentTime =
10197 Math.min(opt_currentTimeOverride || now(), timeoutHandler_endTime);
10198
10199 timeoutHandler_isEnded =
10200 timeoutHandler_currentTime >= timeoutHandler_endTime;
10201
10202 timeoutHandler_offset = duration - (
10203 timeoutHandler_endTime - timeoutHandler_currentTime);
10204
10205 if (tweenable.isPlaying()) {
10206 if (timeoutHandler_isEnded) {
10207 step(targetState, tweenable._attachment, timeoutHandler_offset);
10208 tweenable.stop(true);
10209 } else {
10210 tweenable._scheduleId =
10211 schedule(tweenable._timeoutHandler, UPDATE_TIME);
10212
10213 applyFilter(tweenable, 'beforeTween');
10214
10215 // If the animation has not yet reached the start point (e.g., there was
10216 // delay that has not yet completed), just interpolate the starting
10217 // position of the tween.
10218 if (timeoutHandler_currentTime < (timestamp + delay)) {
10219 tweenProps(1, currentState, originalState, targetState, 1, 1, easing);
10220 } else {
10221 tweenProps(timeoutHandler_currentTime, currentState, originalState,
10222 targetState, duration, timestamp + delay, easing);
10223 }
10224
10225 applyFilter(tweenable, 'afterTween');
10226
10227 step(currentState, tweenable._attachment, timeoutHandler_offset);
10228 }
10229 }
10230 }
10231
10232
10233 /**
10234 * Creates a usable easing Object from a string, a function or another easing
10235 * Object. If `easing` is an Object, then this function clones it and fills
10236 * in the missing properties with `"linear"`.
10237 * @param {Object.<string|Function>} fromTweenParams
10238 * @param {Object|string|Function} easing
10239 * @return {Object.<string|Function>}
10240 * @private
10241 */
10242 function composeEasingObject (fromTweenParams, easing) {
10243 var composedEasing = {};
10244 var typeofEasing = typeof easing;
10245
10246 if (typeofEasing === 'string' || typeofEasing === 'function') {
10247 each(fromTweenParams, function (prop) {
10248 composedEasing[prop] = easing;
10249 });
10250 } else {
10251 each(fromTweenParams, function (prop) {
10252 if (!composedEasing[prop]) {
10253 composedEasing[prop] = easing[prop] || DEFAULT_EASING;
10254 }
10255 });
10256 }
10257
10258 return composedEasing;
10259 }
10260
10261 /**
10262 * NGTweenable constructor.
10263 * @class NGTweenable
10264 * @param {Object=} opt_initialState The values that the initial tween should
10265 * start at if a `from` object is not provided to `{{#crossLink
10266 * "NGTweenable/tween:method"}}{{/crossLink}}` or `{{#crossLink
10267 * "NGTweenable/setConfig:method"}}{{/crossLink}}`.
10268 * @param {Object=} opt_config Configuration object to be passed to
10269 * `{{#crossLink "NGTweenable/setConfig:method"}}{{/crossLink}}`.
10270 * @module NGTweenable
10271 * @constructor
10272 */
10273 function NGTweenable (opt_initialState, opt_config) {
10274 this._currentState = opt_initialState || {};
10275 this._configured = false;
10276 this._scheduleFunction = DEFAULT_SCHEDULE_FUNCTION;
10277
10278 // To prevent unnecessary calls to setConfig do not set default
10279 // configuration here. Only set default configuration immediately before
10280 // tweening if none has been set.
10281 if (typeof opt_config !== 'undefined') {
10282 this.setConfig(opt_config);
10283 }
10284 }
10285
10286 /**
10287 * Configure and start a tween.
10288 * @method tween
10289 * @param {Object=} opt_config Configuration object to be passed to
10290 * `{{#crossLink "NGTweenable/setConfig:method"}}{{/crossLink}}`.
10291 * @chainable
10292 */
10293 NGTweenable.prototype.tween = function (opt_config) {
10294 if (this._isTweening) {
10295 return this;
10296 }
10297
10298 // Only set default config if no configuration has been set previously and
10299 // none is provided now.
10300 if (opt_config !== undefined || !this._configured) {
10301 this.setConfig(opt_config);
10302 }
10303
10304 this._timestamp = now();
10305 this._start(this.get(), this._attachment);
10306 return this.resume();
10307 };
10308
10309 /**
10310 * Configure a tween that will start at some point in the future.
10311 *
10312 * @method setConfig
10313 * @param {Object} config The following values are valid:
10314 * - __from__ (_Object=_): Starting position. If omitted, `{{#crossLink
10315 * "NGTweenable/get:method"}}get(){{/crossLink}}` is used.
10316 * - __to__ (_Object=_): Ending position.
10317 * - __duration__ (_number=_): How many milliseconds to animate for.
10318 * - __delay__ (_delay=_): How many milliseconds to wait before starting the
10319 * tween.
10320 * - __start__ (_Function(Object, *)_): Function to execute when the tween
10321 * begins. Receives the state of the tween as the first parameter and
10322 * `attachment` as the second parameter.
10323 * - __step__ (_Function(Object, *, number)_): Function to execute on every
10324 * tick. Receives `{{#crossLink
10325 * "NGTweenable/get:method"}}get(){{/crossLink}}` as the first parameter,
10326 * `attachment` as the second parameter, and the time elapsed since the
10327 * start of the tween as the third. This function is not called on the
10328 * final step of the animation, but `finish` is.
10329 * - __finish__ (_Function(Object, *)_): Function to execute upon tween
10330 * completion. Receives the state of the tween as the first parameter and
10331 * `attachment` as the second parameter.
10332 * - __easing__ (_Object.<string|Function>|string|Function=_): Easing curve
10333 * name(s) or function(s) to use for the tween.
10334 * - __attachment__ (_*_): Cached value that is passed to the
10335 * `step`/`start`/`finish` methods.
10336 * @chainable
10337 */
10338 NGTweenable.prototype.setConfig = function (config) {
10339 config = config || {};
10340 this._configured = true;
10341
10342 // Attach something to this NGTweenable instance (e.g.: a DOM element, an
10343 // object, a string, etc.);
10344 this._attachment = config.attachment;
10345
10346 // Init the internal state
10347 this._pausedAtTime = null;
10348 this._scheduleId = null;
10349 this._delay = config.delay || 0;
10350 this._start = config.start || noop;
10351 this._step = config.step || noop;
10352 this._finish = config.finish || noop;
10353 this._duration = config.duration || DEFAULT_DURATION;
10354 this._currentState = shallowCopy({}, config.from || this.get());
10355 this._originalState = this.get();
10356 this._targetState = shallowCopy({}, config.to || this.get());
10357
10358 var self = this;
10359 this._timeoutHandler = function () {
10360 timeoutHandler(self,
10361 self._timestamp,
10362 self._delay,
10363 self._duration,
10364 self._currentState,
10365 self._originalState,
10366 self._targetState,
10367 self._easing,
10368 self._step,
10369 self._scheduleFunction
10370 );
10371 };
10372
10373 // Aliases used below
10374 var currentState = this._currentState;
10375 var targetState = this._targetState;
10376
10377 // Ensure that there is always something to tween to.
10378 defaults(targetState, currentState);
10379
10380 this._easing = composeEasingObject(
10381 currentState, config.easing || DEFAULT_EASING);
10382
10383 this._filterArgs =
10384 [currentState, this._originalState, targetState, this._easing];
10385
10386 applyFilter(this, 'tweenCreated');
10387 return this;
10388 };
10389
10390 /**
10391 * @method get
10392 * @return {Object} The current state.
10393 */
10394 NGTweenable.prototype.get = function () {
10395 return shallowCopy({}, this._currentState);
10396 };
10397
10398 /**
10399 * @method set
10400 * @param {Object} state The current state.
10401 */
10402 NGTweenable.prototype.set = function (state) {
10403 this._currentState = state;
10404 };
10405
10406 /**
10407 * Pause a tween. Paused tweens can be resumed from the point at which they
10408 * were paused. This is different from `{{#crossLink
10409 * "NGTweenable/stop:method"}}{{/crossLink}}`, as that method
10410 * causes a tween to start over when it is resumed.
10411 * @method pause
10412 * @chainable
10413 */
10414 NGTweenable.prototype.pause = function () {
10415 this._pausedAtTime = now();
10416 this._isPaused = true;
10417 return this;
10418 };
10419
10420 /**
10421 * Resume a paused tween.
10422 * @method resume
10423 * @chainable
10424 */
10425 NGTweenable.prototype.resume = function () {
10426 if (this._isPaused) {
10427 this._timestamp += now() - this._pausedAtTime;
10428 }
10429
10430 this._isPaused = false;
10431 this._isTweening = true;
10432
10433 this._timeoutHandler();
10434
10435 return this;
10436 };
10437
10438 /**
10439 * Move the state of the animation to a specific point in the tween's
10440 * timeline. If the animation is not running, this will cause the `step`
10441 * handlers to be called.
10442 * @method seek
10443 * @param {millisecond} millisecond The millisecond of the animation to seek
10444 * to. This must not be less than `0`.
10445 * @chainable
10446 */
10447 NGTweenable.prototype.seek = function (millisecond) {
10448 millisecond = Math.max(millisecond, 0);
10449 var currentTime = now();
10450
10451 if ((this._timestamp + millisecond) === 0) {
10452 return this;
10453 }
10454
10455 this._timestamp = currentTime - millisecond;
10456
10457 if (!this.isPlaying()) {
10458 this._isTweening = true;
10459 this._isPaused = false;
10460
10461 // If the animation is not running, call timeoutHandler to make sure that
10462 // any step handlers are run.
10463 timeoutHandler(this,
10464 this._timestamp,
10465 this._delay,
10466 this._duration,
10467 this._currentState,
10468 this._originalState,
10469 this._targetState,
10470 this._easing,
10471 this._step,
10472 this._scheduleFunction,
10473 currentTime
10474 );
10475
10476 this.pause();
10477 }
10478
10479 return this;
10480 };
10481
10482 /**
10483 * Stops and cancels a tween.
10484 * @param {boolean=} gotoEnd If `false` or omitted, the tween just stops at
10485 * its current state, and the `finish` handler is not invoked. If `true`,
10486 * the tweened object's values are instantly set to the target values, and
10487 * `finish` is invoked.
10488 * @method stop
10489 * @chainable
10490 */
10491 NGTweenable.prototype.stop = function (gotoEnd) {
10492 this._isTweening = false;
10493 this._isPaused = false;
10494 this._timeoutHandler = noop;
10495
10496 (root.cancelAnimationFrame ||
10497 root.webkitCancelAnimationFrame ||
10498 root.oCancelAnimationFrame ||
10499 root.msCancelAnimationFrame ||
10500 root.mozCancelRequestAnimationFrame ||
10501 root.clearTimeout)(this._scheduleId);
10502
10503 if (gotoEnd) {
10504 applyFilter(this, 'beforeTween');
10505 tweenProps(
10506 1,
10507 this._currentState,
10508 this._originalState,
10509 this._targetState,
10510 1,
10511 0,
10512 this._easing
10513 );
10514 applyFilter(this, 'afterTween');
10515 applyFilter(this, 'afterTweenEnd');
10516 this._finish.call(this, this._currentState, this._attachment);
10517 }
10518
10519 return this;
10520 };
10521
10522 /**
10523 * @method isPlaying
10524 * @return {boolean} Whether or not a tween is running.
10525 */
10526 NGTweenable.prototype.isPlaying = function () {
10527 return this._isTweening && !this._isPaused;
10528 };
10529
10530 /**
10531 * Set a custom schedule function.
10532 *
10533 * If a custom function is not set,
10534 * [`requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/Web/API/window.requestAnimationFrame)
10535 * is used if available, otherwise
10536 * [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/Window.setTimeout)
10537 * is used.
10538 * @method setScheduleFunction
10539 * @param {Function(Function,number)} scheduleFunction The function to be
10540 * used to schedule the next frame to be rendered.
10541 */
10542 NGTweenable.prototype.setScheduleFunction = function (scheduleFunction) {
10543 this._scheduleFunction = scheduleFunction;
10544 };
10545
10546 /**
10547 * `delete` all "own" properties. Call this when the `NGTweenable` instance
10548 * is no longer needed to free memory.
10549 * @method dispose
10550 */
10551 NGTweenable.prototype.dispose = function () {
10552 var prop;
10553 for (prop in this) {
10554 if (this.hasOwnProperty(prop)) {
10555 delete this[prop];
10556 }
10557 }
10558 };
10559
10560 /**
10561 * Filters are used for transforming the properties of a tween at various
10562 * points in a NGTweenable's life cycle. See the README for more info on this.
10563 * @private
10564 */
10565 NGTweenable.prototype.filter = {};
10566
10567 /**
10568 * This object contains all of the tweens available to Shifty. It is
10569 * extensible - simply attach properties to the `NGTweenable.prototype.formula`
10570 * Object following the same format as `linear`.
10571 *
10572 * `pos` should be a normalized `number` (between 0 and 1).
10573 * @property formula
10574 * @type {Object(function)}
10575 */
10576 NGTweenable.prototype.formula = {
10577 linear: function (pos) {
10578 return pos;
10579 }
10580 };
10581
10582 formula = NGTweenable.prototype.formula;
10583
10584 shallowCopy(NGTweenable, {
10585 'now': now
10586 ,'each': each
10587 ,'tweenProps': tweenProps
10588 ,'tweenProp': tweenProp
10589 ,'applyFilter': applyFilter
10590 ,'shallowCopy': shallowCopy
10591 ,'defaults': defaults
10592 ,'composeEasingObject': composeEasingObject
10593 });
10594
10595 // `root` is provided in the intro/outro files.
10596
10597 // A hook used for unit testing.
10598 if (typeof SHIFTY_DEBUG_NOW === 'function') {
10599 root.timeoutHandler = timeoutHandler;
10600 }
10601
10602 // Bootstrap NGTweenable appropriately for the environment.
10603 if (typeof exports === 'object') {
10604 // CommonJS
10605 module.exports = NGTweenable;
10606 } else if (typeof define === 'function' && define.amdDISABLED) {
10607 // AMD
10608 define(function () {return NGTweenable;});
10609 } else if (typeof root.NGTweenable === 'undefined') {
10610 // Browser: Make `NGTweenable` globally accessible.
10611 root.NGTweenable = NGTweenable;
10612 }
10613
10614 return NGTweenable;
10615
10616} ());
10617
10618/*!
10619 * All equations are adapted from Thomas Fuchs'
10620 * [Scripty2](https://github.com/madrobby/scripty2/blob/master/src/effects/transitions/penner.js).
10621 *
10622 * Based on Easing Equations (c) 2003 [Robert
10623 * Penner](http://www.robertpenner.com/), all rights reserved. This work is
10624 * [subject to terms](http://www.robertpenner.com/easing_terms_of_use.html).
10625 */
10626
10627/*!
10628 * TERMS OF USE - EASING EQUATIONS
10629 * Open source under the BSD License.
10630 * Easing Equations (c) 2003 Robert Penner, all rights reserved.
10631 */
10632
10633;(function () {
10634
10635 NGTweenable.shallowCopy(NGTweenable.prototype.formula, {
10636 easeInQuad: function (pos) {
10637 return Math.pow(pos, 2);
10638 },
10639
10640 easeOutQuad: function (pos) {
10641 return -(Math.pow((pos - 1), 2) - 1);
10642 },
10643
10644 easeInOutQuad: function (pos) {
10645 if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,2);}
10646 return -0.5 * ((pos -= 2) * pos - 2);
10647 },
10648
10649 easeInCubic: function (pos) {
10650 return Math.pow(pos, 3);
10651 },
10652
10653 easeOutCubic: function (pos) {
10654 return (Math.pow((pos - 1), 3) + 1);
10655 },
10656
10657 easeInOutCubic: function (pos) {
10658 if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,3);}
10659 return 0.5 * (Math.pow((pos - 2),3) + 2);
10660 },
10661
10662 easeInQuart: function (pos) {
10663 return Math.pow(pos, 4);
10664 },
10665
10666 easeOutQuart: function (pos) {
10667 return -(Math.pow((pos - 1), 4) - 1);
10668 },
10669
10670 easeInOutQuart: function (pos) {
10671 if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,4);}
10672 return -0.5 * ((pos -= 2) * Math.pow(pos,3) - 2);
10673 },
10674
10675 easeInQuint: function (pos) {
10676 return Math.pow(pos, 5);
10677 },
10678
10679 easeOutQuint: function (pos) {
10680 return (Math.pow((pos - 1), 5) + 1);
10681 },
10682
10683 easeInOutQuint: function (pos) {
10684 if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,5);}
10685 return 0.5 * (Math.pow((pos - 2),5) + 2);
10686 },
10687
10688 easeInSine: function (pos) {
10689 return -Math.cos(pos * (Math.PI / 2)) + 1;
10690 },
10691
10692 easeOutSine: function (pos) {
10693 return Math.sin(pos * (Math.PI / 2));
10694 },
10695
10696 easeInOutSine: function (pos) {
10697 return (-0.5 * (Math.cos(Math.PI * pos) - 1));
10698 },
10699
10700 easeInExpo: function (pos) {
10701 return (pos === 0) ? 0 : Math.pow(2, 10 * (pos - 1));
10702 },
10703
10704 easeOutExpo: function (pos) {
10705 return (pos === 1) ? 1 : -Math.pow(2, -10 * pos) + 1;
10706 },
10707
10708 easeInOutExpo: function (pos) {
10709 if (pos === 0) {return 0;}
10710 if (pos === 1) {return 1;}
10711 if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(2,10 * (pos - 1));}
10712 return 0.5 * (-Math.pow(2, -10 * --pos) + 2);
10713 },
10714
10715 easeInCirc: function (pos) {
10716 return -(Math.sqrt(1 - (pos * pos)) - 1);
10717 },
10718
10719 easeOutCirc: function (pos) {
10720 return Math.sqrt(1 - Math.pow((pos - 1), 2));
10721 },
10722
10723 easeInOutCirc: function (pos) {
10724 if ((pos /= 0.5) < 1) {return -0.5 * (Math.sqrt(1 - pos * pos) - 1);}
10725 return 0.5 * (Math.sqrt(1 - (pos -= 2) * pos) + 1);
10726 },
10727
10728 easeOutBounce: function (pos) {
10729 if ((pos) < (1 / 2.75)) {
10730 return (7.5625 * pos * pos);
10731 } else if (pos < (2 / 2.75)) {
10732 return (7.5625 * (pos -= (1.5 / 2.75)) * pos + 0.75);
10733 } else if (pos < (2.5 / 2.75)) {
10734 return (7.5625 * (pos -= (2.25 / 2.75)) * pos + 0.9375);
10735 } else {
10736 return (7.5625 * (pos -= (2.625 / 2.75)) * pos + 0.984375);
10737 }
10738 },
10739
10740 easeInBack: function (pos) {
10741 var s = 1.70158;
10742 return (pos) * pos * ((s + 1) * pos - s);
10743 },
10744
10745 easeOutBack: function (pos) {
10746 var s = 1.70158;
10747 return (pos = pos - 1) * pos * ((s + 1) * pos + s) + 1;
10748 },
10749
10750 easeInOutBack: function (pos) {
10751 var s = 1.70158;
10752 if ((pos /= 0.5) < 1) {
10753 return 0.5 * (pos * pos * (((s *= (1.525)) + 1) * pos - s));
10754 }
10755 return 0.5 * ((pos -= 2) * pos * (((s *= (1.525)) + 1) * pos + s) + 2);
10756 },
10757
10758 elastic: function (pos) {
10759 // jshint maxlen:90
10760 return -1 * Math.pow(4,-8 * pos) * Math.sin((pos * 6 - 1) * (2 * Math.PI) / 2) + 1;
10761 },
10762
10763 swingFromTo: function (pos) {
10764 var s = 1.70158;
10765 return ((pos /= 0.5) < 1) ?
10766 0.5 * (pos * pos * (((s *= (1.525)) + 1) * pos - s)) :
10767 0.5 * ((pos -= 2) * pos * (((s *= (1.525)) + 1) * pos + s) + 2);
10768 },
10769
10770 swingFrom: function (pos) {
10771 var s = 1.70158;
10772 return pos * pos * ((s + 1) * pos - s);
10773 },
10774
10775 swingTo: function (pos) {
10776 var s = 1.70158;
10777 return (pos -= 1) * pos * ((s + 1) * pos + s) + 1;
10778 },
10779
10780 bounce: function (pos) {
10781 if (pos < (1 / 2.75)) {
10782 return (7.5625 * pos * pos);
10783 } else if (pos < (2 / 2.75)) {
10784 return (7.5625 * (pos -= (1.5 / 2.75)) * pos + 0.75);
10785 } else if (pos < (2.5 / 2.75)) {
10786 return (7.5625 * (pos -= (2.25 / 2.75)) * pos + 0.9375);
10787 } else {
10788 return (7.5625 * (pos -= (2.625 / 2.75)) * pos + 0.984375);
10789 }
10790 },
10791
10792 bouncePast: function (pos) {
10793 if (pos < (1 / 2.75)) {
10794 return (7.5625 * pos * pos);
10795 } else if (pos < (2 / 2.75)) {
10796 return 2 - (7.5625 * (pos -= (1.5 / 2.75)) * pos + 0.75);
10797 } else if (pos < (2.5 / 2.75)) {
10798 return 2 - (7.5625 * (pos -= (2.25 / 2.75)) * pos + 0.9375);
10799 } else {
10800 return 2 - (7.5625 * (pos -= (2.625 / 2.75)) * pos + 0.984375);
10801 }
10802 },
10803
10804 easeFromTo: function (pos) {
10805 if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,4);}
10806 return -0.5 * ((pos -= 2) * Math.pow(pos,3) - 2);
10807 },
10808
10809 easeFrom: function (pos) {
10810 return Math.pow(pos,4);
10811 },
10812
10813 easeTo: function (pos) {
10814 return Math.pow(pos,0.25);
10815 }
10816 });
10817
10818}());
10819
10820// jshint maxlen:100
10821/**
10822 * The Bezier magic in this file is adapted/copied almost wholesale from
10823 * [Scripty2](https://github.com/madrobby/scripty2/blob/master/src/effects/transitions/cubic-bezier.js),
10824 * which was adapted from Apple code (which probably came from
10825 * [here](http://opensource.apple.com/source/WebCore/WebCore-955.66/platform/graphics/UnitBezier.h)).
10826 * Special thanks to Apple and Thomas Fuchs for much of this code.
10827 */
10828
10829/**
10830 * Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
10831 *
10832 * Redistribution and use in source and binary forms, with or without
10833 * modification, are permitted provided that the following conditions are met:
10834 *
10835 * 1. Redistributions of source code must retain the above copyright notice,
10836 * this list of conditions and the following disclaimer.
10837 *
10838 * 2. Redistributions in binary form must reproduce the above copyright notice,
10839 * this list of conditions and the following disclaimer in the documentation
10840 * and/or other materials provided with the distribution.
10841 *
10842 * 3. Neither the name of the copyright holder(s) nor the names of any
10843 * contributors may be used to endorse or promote products derived from
10844 * this software without specific prior written permission.
10845 *
10846 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
10847 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
10848 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
10849 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
10850 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
10851 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
10852 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
10853 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
10854 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
10855 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
10856 * POSSIBILITY OF SUCH DAMAGE.
10857 */
10858;(function () {
10859 // port of webkit cubic bezier handling by http://www.netzgesta.de/dev/
10860 function cubicBezierAtTime(t,p1x,p1y,p2x,p2y,duration) {
10861 var ax = 0,bx = 0,cx = 0,ay = 0,by = 0,cy = 0;
10862 function sampleCurveX(t) {
10863 return ((ax * t + bx) * t + cx) * t;
10864 }
10865 function sampleCurveY(t) {
10866 return ((ay * t + by) * t + cy) * t;
10867 }
10868 function sampleCurveDerivativeX(t) {
10869 return (3.0 * ax * t + 2.0 * bx) * t + cx;
10870 }
10871 function solveEpsilon(duration) {
10872 return 1.0 / (200.0 * duration);
10873 }
10874 function solve(x,epsilon) {
10875 return sampleCurveY(solveCurveX(x, epsilon));
10876 }
10877 function fabs(n) {
10878 if (n >= 0) {
10879 return n;
10880 } else {
10881 return 0 - n;
10882 }
10883 }
10884 function solveCurveX(x, epsilon) {
10885 var t0,t1,t2,x2,d2,i;
10886 for (t2 = x, i = 0; i < 8; i++) {
10887 x2 = sampleCurveX(t2) - x;
10888 if (fabs(x2) < epsilon) {
10889 return t2;
10890 }
10891 d2 = sampleCurveDerivativeX(t2);
10892 if (fabs(d2) < 1e-6) {
10893 break;
10894 }
10895 t2 = t2 - x2 / d2;
10896 }
10897 t0 = 0.0;
10898 t1 = 1.0;
10899 t2 = x;
10900 if (t2 < t0) {
10901 return t0;
10902 }
10903 if (t2 > t1) {
10904 return t1;
10905 }
10906 while (t0 < t1) {
10907 x2 = sampleCurveX(t2);
10908 if (fabs(x2 - x) < epsilon) {
10909 return t2;
10910 }
10911 if (x > x2) {
10912 t0 = t2;
10913 }else {
10914 t1 = t2;
10915 }
10916 t2 = (t1 - t0) * 0.5 + t0;
10917 }
10918 return t2; // Failure.
10919 }
10920 cx = 3.0 * p1x;
10921 bx = 3.0 * (p2x - p1x) - cx;
10922 ax = 1.0 - cx - bx;
10923 cy = 3.0 * p1y;
10924 by = 3.0 * (p2y - p1y) - cy;
10925 ay = 1.0 - cy - by;
10926 return solve(t, solveEpsilon(duration));
10927 }
10928 /**
10929 * getCubicBezierTransition(x1, y1, x2, y2) -> Function
10930 *
10931 * Generates a transition easing function that is compatible
10932 * with WebKit's CSS transitions `-webkit-transition-timing-function`
10933 * CSS property.
10934 *
10935 * The W3C has more information about CSS3 transition timing functions:
10936 * http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag
10937 *
10938 * @param {number} x1
10939 * @param {number} y1
10940 * @param {number} x2
10941 * @param {number} y2
10942 * @return {function}
10943 * @private
10944 */
10945 function getCubicBezierTransition (x1, y1, x2, y2) {
10946 return function (pos) {
10947 return cubicBezierAtTime(pos,x1,y1,x2,y2,1);
10948 };
10949 }
10950 // End ported code
10951
10952 /**
10953 * Create a Bezier easing function and attach it to `{{#crossLink
10954 * "NGTweenable/formula:property"}}NGTweenable#formula{{/crossLink}}`. This
10955 * function gives you total control over the easing curve. Matthew Lein's
10956 * [Ceaser](http://matthewlein.com/ceaser/) is a useful tool for visualizing
10957 * the curves you can make with this function.
10958 * @method setBezierFunction
10959 * @param {string} name The name of the easing curve. Overwrites the old
10960 * easing function on `{{#crossLink
10961 * "NGTweenable/formula:property"}}NGTweenable#formula{{/crossLink}}` if it
10962 * exists.
10963 * @param {number} x1
10964 * @param {number} y1
10965 * @param {number} x2
10966 * @param {number} y2
10967 * @return {function} The easing function that was attached to
10968 * NGTweenable.prototype.formula.
10969 */
10970 NGTweenable.setBezierFunction = function (name, x1, y1, x2, y2) {
10971 var cubicBezierTransition = getCubicBezierTransition(x1, y1, x2, y2);
10972 cubicBezierTransition.displayName = name;
10973 cubicBezierTransition.x1 = x1;
10974 cubicBezierTransition.y1 = y1;
10975 cubicBezierTransition.x2 = x2;
10976 cubicBezierTransition.y2 = y2;
10977
10978 return NGTweenable.prototype.formula[name] = cubicBezierTransition;
10979 };
10980
10981
10982 /**
10983 * `delete` an easing function from `{{#crossLink
10984 * "NGTweenable/formula:property"}}NGTweenable#formula{{/crossLink}}`. Be
10985 * careful with this method, as it `delete`s whatever easing formula matches
10986 * `name` (which means you can delete standard Shifty easing functions).
10987 * @method unsetBezierFunction
10988 * @param {string} name The name of the easing function to delete.
10989 * @return {function}
10990 */
10991 NGTweenable.unsetBezierFunction = function (name) {
10992 delete NGTweenable.prototype.formula[name];
10993 };
10994
10995})();
10996
10997;(function () {
10998
10999 function getInterpolatedValues (
11000 from, current, targetState, position, easing, delay) {
11001 return NGTweenable.tweenProps(
11002 position, current, from, targetState, 1, delay, easing);
11003 }
11004
11005 // Fake a NGTweenable and patch some internals. This approach allows us to
11006 // skip uneccessary processing and object recreation, cutting down on garbage
11007 // collection pauses.
11008 var mockNGTweenable = new NGTweenable();
11009 mockNGTweenable._filterArgs = [];
11010
11011 /**
11012 * Compute the midpoint of two Objects. This method effectively calculates a
11013 * specific frame of animation that `{{#crossLink
11014 * "NGTweenable/tween:method"}}{{/crossLink}}` does many times over the course
11015 * of a full tween.
11016 *
11017 * var interpolatedValues = NGTweenable.interpolate({
11018 * width: '100px',
11019 * opacity: 0,
11020 * color: '#fff'
11021 * }, {
11022 * width: '200px',
11023 * opacity: 1,
11024 * color: '#000'
11025 * }, 0.5);
11026 *
11027 * console.log(interpolatedValues);
11028 * // {opacity: 0.5, width: "150px", color: "rgb(127,127,127)"}
11029 *
11030 * @static
11031 * @method interpolate
11032 * @param {Object} from The starting values to tween from.
11033 * @param {Object} targetState The ending values to tween to.
11034 * @param {number} position The normalized position value (between `0.0` and
11035 * `1.0`) to interpolate the values between `from` and `to` for. `from`
11036 * represents `0` and `to` represents `1`.
11037 * @param {Object.<string|Function>|string|Function} easing The easing
11038 * curve(s) to calculate the midpoint against. You can reference any easing
11039 * function attached to `NGTweenable.prototype.formula`, or provide the easing
11040 * function(s) directly. If omitted, this defaults to "linear".
11041 * @param {number=} opt_delay Optional delay to pad the beginning of the
11042 * interpolated tween with. This increases the range of `position` from (`0`
11043 * through `1`) to (`0` through `1 + opt_delay`). So, a delay of `0.5` would
11044 * increase all valid values of `position` to numbers between `0` and `1.5`.
11045 * @return {Object}
11046 */
11047 NGTweenable.interpolate = function (
11048 from, targetState, position, easing, opt_delay) {
11049
11050 var current = NGTweenable.shallowCopy({}, from);
11051 var delay = opt_delay || 0;
11052 var easingObject = NGTweenable.composeEasingObject(
11053 from, easing || 'linear');
11054
11055 mockNGTweenable.set({});
11056
11057 // Alias and reuse the _filterArgs array instead of recreating it.
11058 var filterArgs = mockNGTweenable._filterArgs;
11059 filterArgs.length = 0;
11060 filterArgs[0] = current;
11061 filterArgs[1] = from;
11062 filterArgs[2] = targetState;
11063 filterArgs[3] = easingObject;
11064
11065 // Any defined value transformation must be applied
11066 NGTweenable.applyFilter(mockNGTweenable, 'tweenCreated');
11067 NGTweenable.applyFilter(mockNGTweenable, 'beforeTween');
11068
11069 var interpolatedValues = getInterpolatedValues(
11070 from, current, targetState, position, easingObject, delay);
11071
11072 // Transform values back into their original format
11073 NGTweenable.applyFilter(mockNGTweenable, 'afterTween');
11074
11075 return interpolatedValues;
11076 };
11077
11078}());
11079
11080/**
11081 * This module adds string interpolation support to Shifty.
11082 *
11083 * The Token extension allows Shifty to tween numbers inside of strings. Among
11084 * other things, this allows you to animate CSS properties. For example, you
11085 * can do this:
11086 *
11087 * var tweenable = new NGTweenable();
11088 * tweenable.tween({
11089 * from: { transform: 'translateX(45px)' },
11090 * to: { transform: 'translateX(90xp)' }
11091 * });
11092 *
11093 * `translateX(45)` will be tweened to `translateX(90)`. To demonstrate:
11094 *
11095 * var tweenable = new NGTweenable();
11096 * tweenable.tween({
11097 * from: { transform: 'translateX(45px)' },
11098 * to: { transform: 'translateX(90px)' },
11099 * step: function (state) {
11100 * console.log(state.transform);
11101 * }
11102 * });
11103 *
11104 * The above snippet will log something like this in the console:
11105 *
11106 * translateX(60.3px)
11107 * ...
11108 * translateX(76.05px)
11109 * ...
11110 * translateX(90px)
11111 *
11112 * Another use for this is animating colors:
11113 *
11114 * var tweenable = new NGTweenable();
11115 * tweenable.tween({
11116 * from: { color: 'rgb(0,255,0)' },
11117 * to: { color: 'rgb(255,0,255)' },
11118 * step: function (state) {
11119 * console.log(state.color);
11120 * }
11121 * });
11122 *
11123 * The above snippet will log something like this:
11124 *
11125 * rgb(84,170,84)
11126 * ...
11127 * rgb(170,84,170)
11128 * ...
11129 * rgb(255,0,255)
11130 *
11131 * This extension also supports hexadecimal colors, in both long (`#ff00ff`)
11132 * and short (`#f0f`) forms. Be aware that hexadecimal input values will be
11133 * converted into the equivalent RGB output values. This is done to optimize
11134 * for performance.
11135 *
11136 * var tweenable = new NGTweenable();
11137 * tweenable.tween({
11138 * from: { color: '#0f0' },
11139 * to: { color: '#f0f' },
11140 * step: function (state) {
11141 * console.log(state.color);
11142 * }
11143 * });
11144 *
11145 * This snippet will generate the same output as the one before it because
11146 * equivalent values were supplied (just in hexadecimal form rather than RGB):
11147 *
11148 * rgb(84,170,84)
11149 * ...
11150 * rgb(170,84,170)
11151 * ...
11152 * rgb(255,0,255)
11153 *
11154 * ## Easing support
11155 *
11156 * Easing works somewhat differently in the Token extension. This is because
11157 * some CSS properties have multiple values in them, and you might need to
11158 * tween each value along its own easing curve. A basic example:
11159 *
11160 * var tweenable = new NGTweenable();
11161 * tweenable.tween({
11162 * from: { transform: 'translateX(0px) translateY(0px)' },
11163 * to: { transform: 'translateX(100px) translateY(100px)' },
11164 * easing: { transform: 'easeInQuad' },
11165 * step: function (state) {
11166 * console.log(state.transform);
11167 * }
11168 * });
11169 *
11170 * The above snippet will create values like this:
11171 *
11172 * translateX(11.56px) translateY(11.56px)
11173 * ...
11174 * translateX(46.24px) translateY(46.24px)
11175 * ...
11176 * translateX(100px) translateY(100px)
11177 *
11178 * In this case, the values for `translateX` and `translateY` are always the
11179 * same for each step of the tween, because they have the same start and end
11180 * points and both use the same easing curve. We can also tween `translateX`
11181 * and `translateY` along independent curves:
11182 *
11183 * var tweenable = new NGTweenable();
11184 * tweenable.tween({
11185 * from: { transform: 'translateX(0px) translateY(0px)' },
11186 * to: { transform: 'translateX(100px) translateY(100px)' },
11187 * easing: { transform: 'easeInQuad bounce' },
11188 * step: function (state) {
11189 * console.log(state.transform);
11190 * }
11191 * });
11192 *
11193 * The above snippet will create values like this:
11194 *
11195 * translateX(10.89px) translateY(82.35px)
11196 * ...
11197 * translateX(44.89px) translateY(86.73px)
11198 * ...
11199 * translateX(100px) translateY(100px)
11200 *
11201 * `translateX` and `translateY` are not in sync anymore, because `easeInQuad`
11202 * was specified for `translateX` and `bounce` for `translateY`. Mixing and
11203 * matching easing curves can make for some interesting motion in your
11204 * animations.
11205 *
11206 * The order of the space-separated easing curves correspond the token values
11207 * they apply to. If there are more token values than easing curves listed,
11208 * the last easing curve listed is used.
11209 * @submodule NGTweenable.token
11210 */
11211
11212// token function is defined above only so that dox-foundation sees it as
11213// documentation and renders it. It is never used, and is optimized away at
11214// build time.
11215
11216;(function (NGTweenable) {
11217
11218 /**
11219 * @typedef {{
11220 * formatString: string
11221 * chunkNames: Array.<string>
11222 * }}
11223 * @private
11224 */
11225 var formatManifest;
11226
11227 // CONSTANTS
11228
11229 var R_NUMBER_COMPONENT = /(\d|\-|\.)/;
11230 var R_FORMAT_CHUNKS = /([^\-0-9\.]+)/g;
11231 var R_UNFORMATTED_VALUES = /[0-9.\-]+/g;
11232 var R_RGB = new RegExp(
11233 'rgb\\(' + R_UNFORMATTED_VALUES.source +
11234 (/,\s*/.source) + R_UNFORMATTED_VALUES.source +
11235 (/,\s*/.source) + R_UNFORMATTED_VALUES.source + '\\)', 'g');
11236 var R_RGB_PREFIX = /^.*\(/;
11237 var R_HEX = /#([0-9]|[a-f]){3,6}/gi;
11238 var VALUE_PLACEHOLDER = 'VAL';
11239
11240 // HELPERS
11241
11242 /**
11243 * @param {Array.number} rawValues
11244 * @param {string} prefix
11245 *
11246 * @return {Array.<string>}
11247 * @private
11248 */
11249 function getFormatChunksFrom (rawValues, prefix) {
11250 var accumulator = [];
11251
11252 var rawValuesLength = rawValues.length;
11253 var i;
11254
11255 for (i = 0; i < rawValuesLength; i++) {
11256 accumulator.push('_' + prefix + '_' + i);
11257 }
11258
11259 return accumulator;
11260 }
11261
11262 /**
11263 * @param {string} formattedString
11264 *
11265 * @return {string}
11266 * @private
11267 */
11268 function getFormatStringFrom (formattedString) {
11269 var chunks = formattedString.match(R_FORMAT_CHUNKS);
11270
11271 if (!chunks) {
11272 // chunks will be null if there were no tokens to parse in
11273 // formattedString (for example, if formattedString is '2'). Coerce
11274 // chunks to be useful here.
11275 chunks = ['', ''];
11276
11277 // If there is only one chunk, assume that the string is a number
11278 // followed by a token...
11279 // NOTE: This may be an unwise assumption.
11280 } else if (chunks.length === 1 ||
11281 // ...or if the string starts with a number component (".", "-", or a
11282 // digit)...
11283 formattedString.charAt(0).match(R_NUMBER_COMPONENT)) {
11284 // ...prepend an empty string here to make sure that the formatted number
11285 // is properly replaced by VALUE_PLACEHOLDER
11286 chunks.unshift('');
11287 }
11288
11289 return chunks.join(VALUE_PLACEHOLDER);
11290 }
11291
11292 /**
11293 * Convert all hex color values within a string to an rgb string.
11294 *
11295 * @param {Object} stateObject
11296 *
11297 * @return {Object} The modified obj
11298 * @private
11299 */
11300 function sanitizeObjectForHexProps (stateObject) {
11301 NGTweenable.each(stateObject, function (prop) {
11302 var currentProp = stateObject[prop];
11303
11304 if (typeof currentProp === 'string' && currentProp.match(R_HEX)) {
11305 stateObject[prop] = sanitizeHexChunksToRGB(currentProp);
11306 }
11307 });
11308 }
11309
11310 /**
11311 * @param {string} str
11312 *
11313 * @return {string}
11314 * @private
11315 */
11316 function sanitizeHexChunksToRGB (str) {
11317 return filterStringChunks(R_HEX, str, convertHexToRGB);
11318 }
11319
11320 /**
11321 * @param {string} hexString
11322 *
11323 * @return {string}
11324 * @private
11325 */
11326 function convertHexToRGB (hexString) {
11327 var rgbArr = hexToRGBArray(hexString);
11328 return 'rgb(' + rgbArr[0] + ',' + rgbArr[1] + ',' + rgbArr[2] + ')';
11329 }
11330
11331 var hexToRGBArray_returnArray = [];
11332 /**
11333 * Convert a hexadecimal string to an array with three items, one each for
11334 * the red, blue, and green decimal values.
11335 *
11336 * @param {string} hex A hexadecimal string.
11337 *
11338 * @returns {Array.<number>} The converted Array of RGB values if `hex` is a
11339 * valid string, or an Array of three 0's.
11340 * @private
11341 */
11342 function hexToRGBArray (hex) {
11343
11344 hex = hex.replace(/#/, '');
11345
11346 // If the string is a shorthand three digit hex notation, normalize it to
11347 // the standard six digit notation
11348 if (hex.length === 3) {
11349 hex = hex.split('');
11350 hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
11351 }
11352
11353 hexToRGBArray_returnArray[0] = hexToDec(hex.substr(0, 2));
11354 hexToRGBArray_returnArray[1] = hexToDec(hex.substr(2, 2));
11355 hexToRGBArray_returnArray[2] = hexToDec(hex.substr(4, 2));
11356
11357 return hexToRGBArray_returnArray;
11358 }
11359
11360 /**
11361 * Convert a base-16 number to base-10.
11362 *
11363 * @param {Number|String} hex The value to convert
11364 *
11365 * @returns {Number} The base-10 equivalent of `hex`.
11366 * @private
11367 */
11368 function hexToDec (hex) {
11369 return parseInt(hex, 16);
11370 }
11371
11372 /**
11373 * Runs a filter operation on all chunks of a string that match a RegExp
11374 *
11375 * @param {RegExp} pattern
11376 * @param {string} unfilteredString
11377 * @param {function(string)} filter
11378 *
11379 * @return {string}
11380 * @private
11381 */
11382 function filterStringChunks (pattern, unfilteredString, filter) {
11383 var pattenMatches = unfilteredString.match(pattern);
11384 var filteredString = unfilteredString.replace(pattern, VALUE_PLACEHOLDER);
11385
11386 if (pattenMatches) {
11387 var pattenMatchesLength = pattenMatches.length;
11388 var currentChunk;
11389
11390 for (var i = 0; i < pattenMatchesLength; i++) {
11391 currentChunk = pattenMatches.shift();
11392 filteredString = filteredString.replace(
11393 VALUE_PLACEHOLDER, filter(currentChunk));
11394 }
11395 }
11396
11397 return filteredString;
11398 }
11399
11400 /**
11401 * Check for floating point values within rgb strings and rounds them.
11402 *
11403 * @param {string} formattedString
11404 *
11405 * @return {string}
11406 * @private
11407 */
11408 function sanitizeRGBChunks (formattedString) {
11409 return filterStringChunks(R_RGB, formattedString, sanitizeRGBChunk);
11410 }
11411
11412 /**
11413 * @param {string} rgbChunk
11414 *
11415 * @return {string}
11416 * @private
11417 */
11418 function sanitizeRGBChunk (rgbChunk) {
11419 var numbers = rgbChunk.match(R_UNFORMATTED_VALUES);
11420 var numbersLength = numbers.length;
11421 var sanitizedString = rgbChunk.match(R_RGB_PREFIX)[0];
11422
11423 for (var i = 0; i < numbersLength; i++) {
11424 sanitizedString += parseInt(numbers[i], 10) + ',';
11425 }
11426
11427 sanitizedString = sanitizedString.slice(0, -1) + ')';
11428
11429 return sanitizedString;
11430 }
11431
11432 /**
11433 * @param {Object} stateObject
11434 *
11435 * @return {Object} An Object of formatManifests that correspond to
11436 * the string properties of stateObject
11437 * @private
11438 */
11439 function getFormatManifests (stateObject) {
11440 var manifestAccumulator = {};
11441
11442 NGTweenable.each(stateObject, function (prop) {
11443 var currentProp = stateObject[prop];
11444
11445 if (typeof currentProp === 'string') {
11446 var rawValues = getValuesFrom(currentProp);
11447
11448 manifestAccumulator[prop] = {
11449 'formatString': getFormatStringFrom(currentProp)
11450 ,'chunkNames': getFormatChunksFrom(rawValues, prop)
11451 };
11452 }
11453 });
11454
11455 return manifestAccumulator;
11456 }
11457
11458 /**
11459 * @param {Object} stateObject
11460 * @param {Object} formatManifests
11461 * @private
11462 */
11463 function expandFormattedProperties (stateObject, formatManifests) {
11464 NGTweenable.each(formatManifests, function (prop) {
11465 var currentProp = stateObject[prop];
11466 var rawValues = getValuesFrom(currentProp);
11467 var rawValuesLength = rawValues.length;
11468
11469 for (var i = 0; i < rawValuesLength; i++) {
11470 stateObject[formatManifests[prop].chunkNames[i]] = +rawValues[i];
11471 }
11472
11473 delete stateObject[prop];
11474 });
11475 }
11476
11477 /**
11478 * @param {Object} stateObject
11479 * @param {Object} formatManifests
11480 * @private
11481 */
11482 function collapseFormattedProperties (stateObject, formatManifests) {
11483 NGTweenable.each(formatManifests, function (prop) {
11484 var currentProp = stateObject[prop];
11485 var formatChunks = extractPropertyChunks(
11486 stateObject, formatManifests[prop].chunkNames);
11487 var valuesList = getValuesList(
11488 formatChunks, formatManifests[prop].chunkNames);
11489 currentProp = getFormattedValues(
11490 formatManifests[prop].formatString, valuesList);
11491 stateObject[prop] = sanitizeRGBChunks(currentProp);
11492 });
11493 }
11494
11495 /**
11496 * @param {Object} stateObject
11497 * @param {Array.<string>} chunkNames
11498 *
11499 * @return {Object} The extracted value chunks.
11500 * @private
11501 */
11502 function extractPropertyChunks (stateObject, chunkNames) {
11503 var extractedValues = {};
11504 var currentChunkName, chunkNamesLength = chunkNames.length;
11505
11506 for (var i = 0; i < chunkNamesLength; i++) {
11507 currentChunkName = chunkNames[i];
11508 extractedValues[currentChunkName] = stateObject[currentChunkName];
11509 delete stateObject[currentChunkName];
11510 }
11511
11512 return extractedValues;
11513 }
11514
11515 var getValuesList_accumulator = [];
11516 /**
11517 * @param {Object} stateObject
11518 * @param {Array.<string>} chunkNames
11519 *
11520 * @return {Array.<number>}
11521 * @private
11522 */
11523 function getValuesList (stateObject, chunkNames) {
11524 getValuesList_accumulator.length = 0;
11525 var chunkNamesLength = chunkNames.length;
11526
11527 for (var i = 0; i < chunkNamesLength; i++) {
11528 getValuesList_accumulator.push(stateObject[chunkNames[i]]);
11529 }
11530
11531 return getValuesList_accumulator;
11532 }
11533
11534 /**
11535 * @param {string} formatString
11536 * @param {Array.<number>} rawValues
11537 *
11538 * @return {string}
11539 * @private
11540 */
11541 function getFormattedValues (formatString, rawValues) {
11542 var formattedValueString = formatString;
11543 var rawValuesLength = rawValues.length;
11544
11545 for (var i = 0; i < rawValuesLength; i++) {
11546 formattedValueString = formattedValueString.replace(
11547 VALUE_PLACEHOLDER, +rawValues[i].toFixed(4));
11548 }
11549
11550 return formattedValueString;
11551 }
11552
11553 /**
11554 * Note: It's the duty of the caller to convert the Array elements of the
11555 * return value into numbers. This is a performance optimization.
11556 *
11557 * @param {string} formattedString
11558 *
11559 * @return {Array.<string>|null}
11560 * @private
11561 */
11562 function getValuesFrom (formattedString) {
11563 return formattedString.match(R_UNFORMATTED_VALUES);
11564 }
11565
11566 /**
11567 * @param {Object} easingObject
11568 * @param {Object} tokenData
11569 * @private
11570 */
11571 function expandEasingObject (easingObject, tokenData) {
11572 NGTweenable.each(tokenData, function (prop) {
11573 var currentProp = tokenData[prop];
11574 var chunkNames = currentProp.chunkNames;
11575 var chunkLength = chunkNames.length;
11576
11577 var easing = easingObject[prop];
11578 var i;
11579
11580 if (typeof easing === 'string') {
11581 var easingChunks = easing.split(' ');
11582 var lastEasingChunk = easingChunks[easingChunks.length - 1];
11583
11584 for (i = 0; i < chunkLength; i++) {
11585 easingObject[chunkNames[i]] = easingChunks[i] || lastEasingChunk;
11586 }
11587
11588 } else {
11589 for (i = 0; i < chunkLength; i++) {
11590 easingObject[chunkNames[i]] = easing;
11591 }
11592 }
11593
11594 delete easingObject[prop];
11595 });
11596 }
11597
11598 /**
11599 * @param {Object} easingObject
11600 * @param {Object} tokenData
11601 * @private
11602 */
11603 function collapseEasingObject (easingObject, tokenData) {
11604 NGTweenable.each(tokenData, function (prop) {
11605 var currentProp = tokenData[prop];
11606 var chunkNames = currentProp.chunkNames;
11607 var chunkLength = chunkNames.length;
11608
11609 var firstEasing = easingObject[chunkNames[0]];
11610 var typeofEasings = typeof firstEasing;
11611
11612 if (typeofEasings === 'string') {
11613 var composedEasingString = '';
11614
11615 for (var i = 0; i < chunkLength; i++) {
11616 composedEasingString += ' ' + easingObject[chunkNames[i]];
11617 delete easingObject[chunkNames[i]];
11618 }
11619
11620 easingObject[prop] = composedEasingString.substr(1);
11621 } else {
11622 easingObject[prop] = firstEasing;
11623 }
11624 });
11625 }
11626
11627 NGTweenable.prototype.filter.token = {
11628 'tweenCreated': function (currentState, fromState, toState, easingObject) {
11629 sanitizeObjectForHexProps(currentState);
11630 sanitizeObjectForHexProps(fromState);
11631 sanitizeObjectForHexProps(toState);
11632 this._tokenData = getFormatManifests(currentState);
11633 },
11634
11635 'beforeTween': function (currentState, fromState, toState, easingObject) {
11636 expandEasingObject(easingObject, this._tokenData);
11637 expandFormattedProperties(currentState, this._tokenData);
11638 expandFormattedProperties(fromState, this._tokenData);
11639 expandFormattedProperties(toState, this._tokenData);
11640 },
11641
11642 'afterTween': function (currentState, fromState, toState, easingObject) {
11643 collapseFormattedProperties(currentState, this._tokenData);
11644 collapseFormattedProperties(fromState, this._tokenData);
11645 collapseFormattedProperties(toState, this._tokenData);
11646 collapseEasingObject(easingObject, this._tokenData);
11647 }
11648 };
11649
11650} (NGTweenable));
11651
11652}).call(null);
11653
11654
11655
11656
11657//##########################################################################################################################
11658//## HAMMER.JS #############################################################################################################
11659//##########################################################################################################################
11660
11661// HAMMER.JS
11662
11663// external module EMBEDED in nanogallery
11664// NGY BUILD:
11665// replace "Hammer" with "NGHammer" (case sensitive)
11666// replace "var SUPPORT_POINTER_EVENTS = prefixed(window, 'PointerEvent') !== undefined;" with "var SUPPORT_POINTER_EVENTS = false;"
11667// replace "define.amd" with "define.amdDISABLED"
11668
11669
11670
11671/*! NGHammer.JS - v2.0.7 - 2016-04-22
11672 * http://hammerjs.github.io/
11673 *
11674 * Copyright (c) 2016 Jorik Tangelder;
11675 * Licensed under the MIT license */
11676(function(window, document, exportName, undefined) {
11677 'use strict';
11678
11679var VENDOR_PREFIXES = ['', 'webkit', 'Moz', 'MS', 'ms', 'o'];
11680var TEST_ELEMENT = document.createElement('div');
11681
11682var TYPE_FUNCTION = 'function';
11683
11684var round = Math.round;
11685var abs = Math.abs;
11686var now = Date.now;
11687
11688/**
11689 * set a timeout with a given scope
11690 * @param {Function} fn
11691 * @param {Number} timeout
11692 * @param {Object} context
11693 * @returns {number}
11694 */
11695function setTimeoutContext(fn, timeout, context) {
11696 return setTimeout(bindFn(fn, context), timeout);
11697}
11698
11699/**
11700 * if the argument is an array, we want to execute the fn on each entry
11701 * if it aint an array we don't want to do a thing.
11702 * this is used by all the methods that accept a single and array argument.
11703 * @param {*|Array} arg
11704 * @param {String} fn
11705 * @param {Object} [context]
11706 * @returns {Boolean}
11707 */
11708function invokeArrayArg(arg, fn, context) {
11709 if (Array.isArray(arg)) {
11710 each(arg, context[fn], context);
11711 return true;
11712 }
11713 return false;
11714}
11715
11716/**
11717 * walk objects and arrays
11718 * @param {Object} obj
11719 * @param {Function} iterator
11720 * @param {Object} context
11721 */
11722function each(obj, iterator, context) {
11723 var i;
11724
11725 if (!obj) {
11726 return;
11727 }
11728
11729 if (obj.forEach) {
11730 obj.forEach(iterator, context);
11731 } else if (obj.length !== undefined) {
11732 i = 0;
11733 while (i < obj.length) {
11734 iterator.call(context, obj[i], i, obj);
11735 i++;
11736 }
11737 } else {
11738 for (i in obj) {
11739 obj.hasOwnProperty(i) && iterator.call(context, obj[i], i, obj);
11740 }
11741 }
11742}
11743
11744/**
11745 * wrap a method with a deprecation warning and stack trace
11746 * @param {Function} method
11747 * @param {String} name
11748 * @param {String} message
11749 * @returns {Function} A new function wrapping the supplied method.
11750 */
11751function deprecate(method, name, message) {
11752 var deprecationMessage = 'DEPRECATED METHOD: ' + name + '\n' + message + ' AT \n';
11753 return function() {
11754 var e = new Error('get-stack-trace');
11755 var stack = e && e.stack ? e.stack.replace(/^[^\(]+?[\n$]/gm, '')
11756 .replace(/^\s+at\s+/gm, '')
11757 .replace(/^Object.<anonymous>\s*\(/gm, '{anonymous}()@') : 'Unknown Stack Trace';
11758
11759 var log = window.console && (window.console.warn || window.console.log);
11760 if (log) {
11761 log.call(window.console, deprecationMessage, stack);
11762 }
11763 return method.apply(this, arguments);
11764 };
11765}
11766
11767/**
11768 * extend object.
11769 * means that properties in dest will be overwritten by the ones in src.
11770 * @param {Object} target
11771 * @param {...Object} objects_to_assign
11772 * @returns {Object} target
11773 */
11774var assign;
11775if (typeof Object.assign !== 'function') {
11776 assign = function assign(target) {
11777 if (target === undefined || target === null) {
11778 throw new TypeError('Cannot convert undefined or null to object');
11779 }
11780
11781 var output = Object(target);
11782 for (var index = 1; index < arguments.length; index++) {
11783 var source = arguments[index];
11784 if (source !== undefined && source !== null) {
11785 for (var nextKey in source) {
11786 if (source.hasOwnProperty(nextKey)) {
11787 output[nextKey] = source[nextKey];
11788 }
11789 }
11790 }
11791 }
11792 return output;
11793 };
11794} else {
11795 assign = Object.assign;
11796}
11797
11798/**
11799 * extend object.
11800 * means that properties in dest will be overwritten by the ones in src.
11801 * @param {Object} dest
11802 * @param {Object} src
11803 * @param {Boolean} [merge=false]
11804 * @returns {Object} dest
11805 */
11806var extend = deprecate(function extend(dest, src, merge) {
11807 var keys = Object.keys(src);
11808 var i = 0;
11809 while (i < keys.length) {
11810 if (!merge || (merge && dest[keys[i]] === undefined)) {
11811 dest[keys[i]] = src[keys[i]];
11812 }
11813 i++;
11814 }
11815 return dest;
11816}, 'extend', 'Use `assign`.');
11817
11818/**
11819 * merge the values from src in the dest.
11820 * means that properties that exist in dest will not be overwritten by src
11821 * @param {Object} dest
11822 * @param {Object} src
11823 * @returns {Object} dest
11824 */
11825var merge = deprecate(function merge(dest, src) {
11826 return extend(dest, src, true);
11827}, 'merge', 'Use `assign`.');
11828
11829/**
11830 * simple class inheritance
11831 * @param {Function} child
11832 * @param {Function} base
11833 * @param {Object} [properties]
11834 */
11835function inherit(child, base, properties) {
11836 var baseP = base.prototype,
11837 childP;
11838
11839 childP = child.prototype = Object.create(baseP);
11840 childP.constructor = child;
11841 childP._super = baseP;
11842
11843 if (properties) {
11844 assign(childP, properties);
11845 }
11846}
11847
11848/**
11849 * simple function bind
11850 * @param {Function} fn
11851 * @param {Object} context
11852 * @returns {Function}
11853 */
11854function bindFn(fn, context) {
11855 return function boundFn() {
11856 return fn.apply(context, arguments);
11857 };
11858}
11859
11860/**
11861 * let a boolean value also be a function that must return a boolean
11862 * this first item in args will be used as the context
11863 * @param {Boolean|Function} val
11864 * @param {Array} [args]
11865 * @returns {Boolean}
11866 */
11867function boolOrFn(val, args) {
11868 if (typeof val == TYPE_FUNCTION) {
11869 return val.apply(args ? args[0] || undefined : undefined, args);
11870 }
11871 return val;
11872}
11873
11874/**
11875 * use the val2 when val1 is undefined
11876 * @param {*} val1
11877 * @param {*} val2
11878 * @returns {*}
11879 */
11880function ifUndefined(val1, val2) {
11881 return (val1 === undefined) ? val2 : val1;
11882}
11883
11884/**
11885 * addEventListener with multiple events at once
11886 * @param {EventTarget} target
11887 * @param {String} types
11888 * @param {Function} handler
11889 */
11890function addEventListeners(target, types, handler) {
11891 each(splitStr(types), function(type) {
11892 target.addEventListener(type, handler, false);
11893 });
11894}
11895
11896/**
11897 * removeEventListener with multiple events at once
11898 * @param {EventTarget} target
11899 * @param {String} types
11900 * @param {Function} handler
11901 */
11902function removeEventListeners(target, types, handler) {
11903 each(splitStr(types), function(type) {
11904 target.removeEventListener(type, handler, false);
11905 });
11906}
11907
11908/**
11909 * find if a node is in the given parent
11910 * @method hasParent
11911 * @param {HTMLElement} node
11912 * @param {HTMLElement} parent
11913 * @return {Boolean} found
11914 */
11915function hasParent(node, parent) {
11916 while (node) {
11917 if (node == parent) {
11918 return true;
11919 }
11920 node = node.parentNode;
11921 }
11922 return false;
11923}
11924
11925/**
11926 * small indexOf wrapper
11927 * @param {String} str
11928 * @param {String} find
11929 * @returns {Boolean} found
11930 */
11931function inStr(str, find) {
11932 return str.indexOf(find) > -1;
11933}
11934
11935/**
11936 * split string on whitespace
11937 * @param {String} str
11938 * @returns {Array} words
11939 */
11940function splitStr(str) {
11941 return str.trim().split(/\s+/g);
11942}
11943
11944/**
11945 * find if a array contains the object using indexOf or a simple polyFill
11946 * @param {Array} src
11947 * @param {String} find
11948 * @param {String} [findByKey]
11949 * @return {Boolean|Number} false when not found, or the index
11950 */
11951function inArray(src, find, findByKey) {
11952 if (src.indexOf && !findByKey) {
11953 return src.indexOf(find);
11954 } else {
11955 var i = 0;
11956 while (i < src.length) {
11957 if ((findByKey && src[i][findByKey] == find) || (!findByKey && src[i] === find)) {
11958 return i;
11959 }
11960 i++;
11961 }
11962 return -1;
11963 }
11964}
11965
11966/**
11967 * convert array-like objects to real arrays
11968 * @param {Object} obj
11969 * @returns {Array}
11970 */
11971function toArray(obj) {
11972 return Array.prototype.slice.call(obj, 0);
11973}
11974
11975/**
11976 * unique array with objects based on a key (like 'id') or just by the array's value
11977 * @param {Array} src [{id:1},{id:2},{id:1}]
11978 * @param {String} [key]
11979 * @param {Boolean} [sort=False]
11980 * @returns {Array} [{id:1},{id:2}]
11981 */
11982function uniqueArray(src, key, sort) {
11983 var results = [];
11984 var values = [];
11985 var i = 0;
11986
11987 while (i < src.length) {
11988 var val = key ? src[i][key] : src[i];
11989 if (inArray(values, val) < 0) {
11990 results.push(src[i]);
11991 }
11992 values[i] = val;
11993 i++;
11994 }
11995
11996 if (sort) {
11997 if (!key) {
11998 results = results.sort();
11999 } else {
12000 results = results.sort(function sortUniqueArray(a, b) {
12001 return a[key] > b[key];
12002 });
12003 }
12004 }
12005
12006 return results;
12007}
12008
12009/**
12010 * get the prefixed property
12011 * @param {Object} obj
12012 * @param {String} property
12013 * @returns {String|Undefined} prefixed
12014 */
12015function prefixed(obj, property) {
12016 var prefix, prop;
12017 var camelProp = property[0].toUpperCase() + property.slice(1);
12018
12019 var i = 0;
12020 while (i < VENDOR_PREFIXES.length) {
12021 prefix = VENDOR_PREFIXES[i];
12022 prop = (prefix) ? prefix + camelProp : property;
12023
12024 if (prop in obj) {
12025 return prop;
12026 }
12027 i++;
12028 }
12029 return undefined;
12030}
12031
12032/**
12033 * get a unique id
12034 * @returns {number} uniqueId
12035 */
12036var _uniqueId = 1;
12037function uniqueId() {
12038 return _uniqueId++;
12039}
12040
12041/**
12042 * get the window object of an element
12043 * @param {HTMLElement} element
12044 * @returns {DocumentView|Window}
12045 */
12046function getWindowForElement(element) {
12047 var doc = element.ownerDocument || element;
12048 return (doc.defaultView || doc.parentWindow || window);
12049}
12050
12051var MOBILE_REGEX = /mobile|tablet|ip(ad|hone|od)|android/i;
12052
12053var SUPPORT_TOUCH = ('ontouchstart' in window);
12054// var SUPPORT_POINTER_EVENTS = prefixed(window, 'PointerEvent') !== undefined;
12055var SUPPORT_POINTER_EVENTS = false;
12056var SUPPORT_ONLY_TOUCH = SUPPORT_TOUCH && MOBILE_REGEX.test(navigator.userAgent);
12057
12058var INPUT_TYPE_TOUCH = 'touch';
12059var INPUT_TYPE_PEN = 'pen';
12060var INPUT_TYPE_MOUSE = 'mouse';
12061var INPUT_TYPE_KINECT = 'kinect';
12062
12063var COMPUTE_INTERVAL = 25;
12064
12065var INPUT_START = 1;
12066var INPUT_MOVE = 2;
12067var INPUT_END = 4;
12068var INPUT_CANCEL = 8;
12069
12070var DIRECTION_NONE = 1;
12071var DIRECTION_LEFT = 2;
12072var DIRECTION_RIGHT = 4;
12073var DIRECTION_UP = 8;
12074var DIRECTION_DOWN = 16;
12075
12076var DIRECTION_HORIZONTAL = DIRECTION_LEFT | DIRECTION_RIGHT;
12077var DIRECTION_VERTICAL = DIRECTION_UP | DIRECTION_DOWN;
12078var DIRECTION_ALL = DIRECTION_HORIZONTAL | DIRECTION_VERTICAL;
12079
12080var PROPS_XY = ['x', 'y'];
12081var PROPS_CLIENT_XY = ['clientX', 'clientY'];
12082
12083/**
12084 * create new input type manager
12085 * @param {Manager} manager
12086 * @param {Function} callback
12087 * @returns {Input}
12088 * @constructor
12089 */
12090function Input(manager, callback) {
12091 var self = this;
12092 this.manager = manager;
12093 this.callback = callback;
12094 this.element = manager.element;
12095 this.target = manager.options.inputTarget;
12096
12097 // smaller wrapper around the handler, for the scope and the enabled state of the manager,
12098 // so when disabled the input events are completely bypassed.
12099 this.domHandler = function(ev) {
12100 if (boolOrFn(manager.options.enable, [manager])) {
12101 self.handler(ev);
12102 }
12103 };
12104
12105 this.init();
12106
12107}
12108
12109Input.prototype = {
12110 /**
12111 * should handle the inputEvent data and trigger the callback
12112 * @virtual
12113 */
12114 handler: function() { },
12115
12116 /**
12117 * bind the events
12118 */
12119 init: function() {
12120 this.evEl && addEventListeners(this.element, this.evEl, this.domHandler);
12121 this.evTarget && addEventListeners(this.target, this.evTarget, this.domHandler);
12122 this.evWin && addEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler);
12123 },
12124
12125 /**
12126 * unbind the events
12127 */
12128 destroy: function() {
12129 this.evEl && removeEventListeners(this.element, this.evEl, this.domHandler);
12130 this.evTarget && removeEventListeners(this.target, this.evTarget, this.domHandler);
12131 this.evWin && removeEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler);
12132 }
12133};
12134
12135/**
12136 * create new input type manager
12137 * called by the Manager constructor
12138 * @param {NGHammer} manager
12139 * @returns {Input}
12140 */
12141function createInputInstance(manager) {
12142 var Type;
12143 var inputClass = manager.options.inputClass;
12144
12145 if (inputClass) {
12146 Type = inputClass;
12147 } else if (SUPPORT_POINTER_EVENTS) {
12148 Type = PointerEventInput;
12149 } else if (SUPPORT_ONLY_TOUCH) {
12150 Type = TouchInput;
12151 } else if (!SUPPORT_TOUCH) {
12152 Type = MouseInput;
12153 } else {
12154 Type = TouchMouseInput;
12155 }
12156 return new (Type)(manager, inputHandler);
12157}
12158
12159/**
12160 * handle input events
12161 * @param {Manager} manager
12162 * @param {String} eventType
12163 * @param {Object} input
12164 */
12165function inputHandler(manager, eventType, input) {
12166 var pointersLen = input.pointers.length;
12167 var changedPointersLen = input.changedPointers.length;
12168 var isFirst = (eventType & INPUT_START && (pointersLen - changedPointersLen === 0));
12169 var isFinal = (eventType & (INPUT_END | INPUT_CANCEL) && (pointersLen - changedPointersLen === 0));
12170
12171 input.isFirst = !!isFirst;
12172 input.isFinal = !!isFinal;
12173
12174 if (isFirst) {
12175 manager.session = {};
12176 }
12177
12178 // source event is the normalized value of the domEvents
12179 // like 'touchstart, mouseup, pointerdown'
12180 input.eventType = eventType;
12181
12182 // compute scale, rotation etc
12183 computeInputData(manager, input);
12184
12185 // emit secret event
12186 manager.emit('hammer.input', input);
12187
12188 manager.recognize(input);
12189 manager.session.prevInput = input;
12190}
12191
12192/**
12193 * extend the data with some usable properties like scale, rotate, velocity etc
12194 * @param {Object} manager
12195 * @param {Object} input
12196 */
12197function computeInputData(manager, input) {
12198 var session = manager.session;
12199 var pointers = input.pointers;
12200 var pointersLength = pointers.length;
12201
12202 // store the first input to calculate the distance and direction
12203 if (!session.firstInput) {
12204 session.firstInput = simpleCloneInputData(input);
12205 }
12206
12207 // to compute scale and rotation we need to store the multiple touches
12208 if (pointersLength > 1 && !session.firstMultiple) {
12209 session.firstMultiple = simpleCloneInputData(input);
12210 } else if (pointersLength === 1) {
12211 session.firstMultiple = false;
12212 }
12213
12214 var firstInput = session.firstInput;
12215 var firstMultiple = session.firstMultiple;
12216 var offsetCenter = firstMultiple ? firstMultiple.center : firstInput.center;
12217
12218 var center = input.center = getCenter(pointers);
12219 input.timeStamp = now();
12220 input.deltaTime = input.timeStamp - firstInput.timeStamp;
12221
12222 input.angle = getAngle(offsetCenter, center);
12223 input.distance = getDistance(offsetCenter, center);
12224
12225 computeDeltaXY(session, input);
12226 input.offsetDirection = getDirection(input.deltaX, input.deltaY);
12227
12228 var overallVelocity = getVelocity(input.deltaTime, input.deltaX, input.deltaY);
12229 input.overallVelocityX = overallVelocity.x;
12230 input.overallVelocityY = overallVelocity.y;
12231 input.overallVelocity = (abs(overallVelocity.x) > abs(overallVelocity.y)) ? overallVelocity.x : overallVelocity.y;
12232
12233 input.scale = firstMultiple ? getScale(firstMultiple.pointers, pointers) : 1;
12234 input.rotation = firstMultiple ? getRotation(firstMultiple.pointers, pointers) : 0;
12235
12236 input.maxPointers = !session.prevInput ? input.pointers.length : ((input.pointers.length >
12237 session.prevInput.maxPointers) ? input.pointers.length : session.prevInput.maxPointers);
12238
12239 computeIntervalInputData(session, input);
12240
12241 // find the correct target
12242 var target = manager.element;
12243 if (hasParent(input.srcEvent.target, target)) {
12244 target = input.srcEvent.target;
12245 }
12246 input.target = target;
12247}
12248
12249function computeDeltaXY(session, input) {
12250 var center = input.center;
12251 var offset = session.offsetDelta || {};
12252 var prevDelta = session.prevDelta || {};
12253 var prevInput = session.prevInput || {};
12254
12255 if (input.eventType === INPUT_START || prevInput.eventType === INPUT_END) {
12256 prevDelta = session.prevDelta = {
12257 x: prevInput.deltaX || 0,
12258 y: prevInput.deltaY || 0
12259 };
12260
12261 offset = session.offsetDelta = {
12262 x: center.x,
12263 y: center.y
12264 };
12265 }
12266
12267 input.deltaX = prevDelta.x + (center.x - offset.x);
12268 input.deltaY = prevDelta.y + (center.y - offset.y);
12269}
12270
12271/**
12272 * velocity is calculated every x ms
12273 * @param {Object} session
12274 * @param {Object} input
12275 */
12276function computeIntervalInputData(session, input) {
12277 var last = session.lastInterval || input,
12278 deltaTime = input.timeStamp - last.timeStamp,
12279 velocity, velocityX, velocityY, direction;
12280
12281 if (input.eventType != INPUT_CANCEL && (deltaTime > COMPUTE_INTERVAL || last.velocity === undefined)) {
12282 var deltaX = input.deltaX - last.deltaX;
12283 var deltaY = input.deltaY - last.deltaY;
12284
12285 var v = getVelocity(deltaTime, deltaX, deltaY);
12286 velocityX = v.x;
12287 velocityY = v.y;
12288 velocity = (abs(v.x) > abs(v.y)) ? v.x : v.y;
12289 direction = getDirection(deltaX, deltaY);
12290
12291 session.lastInterval = input;
12292 } else {
12293 // use latest velocity info if it doesn't overtake a minimum period
12294 velocity = last.velocity;
12295 velocityX = last.velocityX;
12296 velocityY = last.velocityY;
12297 direction = last.direction;
12298 }
12299
12300 input.velocity = velocity;
12301 input.velocityX = velocityX;
12302 input.velocityY = velocityY;
12303 input.direction = direction;
12304}
12305
12306/**
12307 * create a simple clone from the input used for storage of firstInput and firstMultiple
12308 * @param {Object} input
12309 * @returns {Object} clonedInputData
12310 */
12311function simpleCloneInputData(input) {
12312 // make a simple copy of the pointers because we will get a reference if we don't
12313 // we only need clientXY for the calculations
12314 var pointers = [];
12315 var i = 0;
12316 while (i < input.pointers.length) {
12317 pointers[i] = {
12318 clientX: round(input.pointers[i].clientX),
12319 clientY: round(input.pointers[i].clientY)
12320 };
12321 i++;
12322 }
12323
12324 return {
12325 timeStamp: now(),
12326 pointers: pointers,
12327 center: getCenter(pointers),
12328 deltaX: input.deltaX,
12329 deltaY: input.deltaY
12330 };
12331}
12332
12333/**
12334 * get the center of all the pointers
12335 * @param {Array} pointers
12336 * @return {Object} center contains `x` and `y` properties
12337 */
12338function getCenter(pointers) {
12339 var pointersLength = pointers.length;
12340
12341 // no need to loop when only one touch
12342 if (pointersLength === 1) {
12343 return {
12344 x: round(pointers[0].clientX),
12345 y: round(pointers[0].clientY)
12346 };
12347 }
12348
12349 var x = 0, y = 0, i = 0;
12350 while (i < pointersLength) {
12351 x += pointers[i].clientX;
12352 y += pointers[i].clientY;
12353 i++;
12354 }
12355
12356 return {
12357 x: round(x / pointersLength),
12358 y: round(y / pointersLength)
12359 };
12360}
12361
12362/**
12363 * calculate the velocity between two points. unit is in px per ms.
12364 * @param {Number} deltaTime
12365 * @param {Number} x
12366 * @param {Number} y
12367 * @return {Object} velocity `x` and `y`
12368 */
12369function getVelocity(deltaTime, x, y) {
12370 return {
12371 x: x / deltaTime || 0,
12372 y: y / deltaTime || 0
12373 };
12374}
12375
12376/**
12377 * get the direction between two points
12378 * @param {Number} x
12379 * @param {Number} y
12380 * @return {Number} direction
12381 */
12382function getDirection(x, y) {
12383 if (x === y) {
12384 return DIRECTION_NONE;
12385 }
12386
12387 if (abs(x) >= abs(y)) {
12388 return x < 0 ? DIRECTION_LEFT : DIRECTION_RIGHT;
12389 }
12390 return y < 0 ? DIRECTION_UP : DIRECTION_DOWN;
12391}
12392
12393/**
12394 * calculate the absolute distance between two points
12395 * @param {Object} p1 {x, y}
12396 * @param {Object} p2 {x, y}
12397 * @param {Array} [props] containing x and y keys
12398 * @return {Number} distance
12399 */
12400function getDistance(p1, p2, props) {
12401 if (!props) {
12402 props = PROPS_XY;
12403 }
12404 var x = p2[props[0]] - p1[props[0]],
12405 y = p2[props[1]] - p1[props[1]];
12406
12407 return Math.sqrt((x * x) + (y * y));
12408}
12409
12410/**
12411 * calculate the angle between two coordinates
12412 * @param {Object} p1
12413 * @param {Object} p2
12414 * @param {Array} [props] containing x and y keys
12415 * @return {Number} angle
12416 */
12417function getAngle(p1, p2, props) {
12418 if (!props) {
12419 props = PROPS_XY;
12420 }
12421 var x = p2[props[0]] - p1[props[0]],
12422 y = p2[props[1]] - p1[props[1]];
12423 return Math.atan2(y, x) * 180 / Math.PI;
12424}
12425
12426/**
12427 * calculate the rotation degrees between two pointersets
12428 * @param {Array} start array of pointers
12429 * @param {Array} end array of pointers
12430 * @return {Number} rotation
12431 */
12432function getRotation(start, end) {
12433 return getAngle(end[1], end[0], PROPS_CLIENT_XY) + getAngle(start[1], start[0], PROPS_CLIENT_XY);
12434}
12435
12436/**
12437 * calculate the scale factor between two pointersets
12438 * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out
12439 * @param {Array} start array of pointers
12440 * @param {Array} end array of pointers
12441 * @return {Number} scale
12442 */
12443function getScale(start, end) {
12444 return getDistance(end[0], end[1], PROPS_CLIENT_XY) / getDistance(start[0], start[1], PROPS_CLIENT_XY);
12445}
12446
12447var MOUSE_INPUT_MAP = {
12448 mousedown: INPUT_START,
12449 mousemove: INPUT_MOVE,
12450 mouseup: INPUT_END
12451};
12452
12453var MOUSE_ELEMENT_EVENTS = 'mousedown';
12454var MOUSE_WINDOW_EVENTS = 'mousemove mouseup';
12455
12456/**
12457 * Mouse events input
12458 * @constructor
12459 * @extends Input
12460 */
12461function MouseInput() {
12462 this.evEl = MOUSE_ELEMENT_EVENTS;
12463 this.evWin = MOUSE_WINDOW_EVENTS;
12464
12465 this.pressed = false; // mousedown state
12466
12467 Input.apply(this, arguments);
12468}
12469
12470inherit(MouseInput, Input, {
12471 /**
12472 * handle mouse events
12473 * @param {Object} ev
12474 */
12475 handler: function MEhandler(ev) {
12476 var eventType = MOUSE_INPUT_MAP[ev.type];
12477
12478 // on start we want to have the left mouse button down
12479 if (eventType & INPUT_START && ev.button === 0) {
12480 this.pressed = true;
12481 }
12482
12483 if (eventType & INPUT_MOVE && ev.which !== 1) {
12484 eventType = INPUT_END;
12485 }
12486
12487 // mouse must be down
12488 if (!this.pressed) {
12489 return;
12490 }
12491
12492 if (eventType & INPUT_END) {
12493 this.pressed = false;
12494 }
12495
12496 this.callback(this.manager, eventType, {
12497 pointers: [ev],
12498 changedPointers: [ev],
12499 pointerType: INPUT_TYPE_MOUSE,
12500 srcEvent: ev
12501 });
12502 }
12503});
12504
12505var POINTER_INPUT_MAP = {
12506 pointerdown: INPUT_START,
12507 pointermove: INPUT_MOVE,
12508 pointerup: INPUT_END,
12509 pointercancel: INPUT_CANCEL,
12510 pointerout: INPUT_CANCEL
12511};
12512
12513// in IE10 the pointer types is defined as an enum
12514var IE10_POINTER_TYPE_ENUM = {
12515 2: INPUT_TYPE_TOUCH,
12516 3: INPUT_TYPE_PEN,
12517 4: INPUT_TYPE_MOUSE,
12518 5: INPUT_TYPE_KINECT // see https://twitter.com/jacobrossi/status/480596438489890816
12519};
12520
12521var POINTER_ELEMENT_EVENTS = 'pointerdown';
12522var POINTER_WINDOW_EVENTS = 'pointermove pointerup pointercancel';
12523
12524// IE10 has prefixed support, and case-sensitive
12525if (window.MSPointerEvent && !window.PointerEvent) {
12526 POINTER_ELEMENT_EVENTS = 'MSPointerDown';
12527 POINTER_WINDOW_EVENTS = 'MSPointerMove MSPointerUp MSPointerCancel';
12528}
12529
12530/**
12531 * Pointer events input
12532 * @constructor
12533 * @extends Input
12534 */
12535function PointerEventInput() {
12536 this.evEl = POINTER_ELEMENT_EVENTS;
12537 this.evWin = POINTER_WINDOW_EVENTS;
12538
12539 Input.apply(this, arguments);
12540
12541 this.store = (this.manager.session.pointerEvents = []);
12542}
12543
12544inherit(PointerEventInput, Input, {
12545 /**
12546 * handle mouse events
12547 * @param {Object} ev
12548 */
12549 handler: function PEhandler(ev) {
12550 var store = this.store;
12551 var removePointer = false;
12552
12553 var eventTypeNormalized = ev.type.toLowerCase().replace('ms', '');
12554 var eventType = POINTER_INPUT_MAP[eventTypeNormalized];
12555 var pointerType = IE10_POINTER_TYPE_ENUM[ev.pointerType] || ev.pointerType;
12556
12557 var isTouch = (pointerType == INPUT_TYPE_TOUCH);
12558
12559 // get index of the event in the store
12560 var storeIndex = inArray(store, ev.pointerId, 'pointerId');
12561
12562 // start and mouse must be down
12563 if (eventType & INPUT_START && (ev.button === 0 || isTouch)) {
12564 if (storeIndex < 0) {
12565 store.push(ev);
12566 storeIndex = store.length - 1;
12567 }
12568 } else if (eventType & (INPUT_END | INPUT_CANCEL)) {
12569 removePointer = true;
12570 }
12571
12572 // it not found, so the pointer hasn't been down (so it's probably a hover)
12573 if (storeIndex < 0) {
12574 return;
12575 }
12576
12577 // update the event in the store
12578 store[storeIndex] = ev;
12579
12580 this.callback(this.manager, eventType, {
12581 pointers: store,
12582 changedPointers: [ev],
12583 pointerType: pointerType,
12584 srcEvent: ev
12585 });
12586
12587 if (removePointer) {
12588 // remove from the store
12589 store.splice(storeIndex, 1);
12590 }
12591 }
12592});
12593
12594var SINGLE_TOUCH_INPUT_MAP = {
12595 touchstart: INPUT_START,
12596 touchmove: INPUT_MOVE,
12597 touchend: INPUT_END,
12598 touchcancel: INPUT_CANCEL
12599};
12600
12601var SINGLE_TOUCH_TARGET_EVENTS = 'touchstart';
12602var SINGLE_TOUCH_WINDOW_EVENTS = 'touchstart touchmove touchend touchcancel';
12603
12604/**
12605 * Touch events input
12606 * @constructor
12607 * @extends Input
12608 */
12609function SingleTouchInput() {
12610 this.evTarget = SINGLE_TOUCH_TARGET_EVENTS;
12611 this.evWin = SINGLE_TOUCH_WINDOW_EVENTS;
12612 this.started = false;
12613
12614 Input.apply(this, arguments);
12615}
12616
12617inherit(SingleTouchInput, Input, {
12618 handler: function TEhandler(ev) {
12619 var type = SINGLE_TOUCH_INPUT_MAP[ev.type];
12620
12621 // should we handle the touch events?
12622 if (type === INPUT_START) {
12623 this.started = true;
12624 }
12625
12626 if (!this.started) {
12627 return;
12628 }
12629
12630 var touches = normalizeSingleTouches.call(this, ev, type);
12631
12632 // when done, reset the started state
12633 if (type & (INPUT_END | INPUT_CANCEL) && touches[0].length - touches[1].length === 0) {
12634 this.started = false;
12635 }
12636
12637 this.callback(this.manager, type, {
12638 pointers: touches[0],
12639 changedPointers: touches[1],
12640 pointerType: INPUT_TYPE_TOUCH,
12641 srcEvent: ev
12642 });
12643 }
12644});
12645
12646/**
12647 * @this {TouchInput}
12648 * @param {Object} ev
12649 * @param {Number} type flag
12650 * @returns {undefined|Array} [all, changed]
12651 */
12652function normalizeSingleTouches(ev, type) {
12653 var all = toArray(ev.touches);
12654 var changed = toArray(ev.changedTouches);
12655
12656 if (type & (INPUT_END | INPUT_CANCEL)) {
12657 all = uniqueArray(all.concat(changed), 'identifier', true);
12658 }
12659
12660 return [all, changed];
12661}
12662
12663var TOUCH_INPUT_MAP = {
12664 touchstart: INPUT_START,
12665 touchmove: INPUT_MOVE,
12666 touchend: INPUT_END,
12667 touchcancel: INPUT_CANCEL
12668};
12669
12670var TOUCH_TARGET_EVENTS = 'touchstart touchmove touchend touchcancel';
12671
12672/**
12673 * Multi-user touch events input
12674 * @constructor
12675 * @extends Input
12676 */
12677function TouchInput() {
12678 this.evTarget = TOUCH_TARGET_EVENTS;
12679 this.targetIds = {};
12680
12681 Input.apply(this, arguments);
12682}
12683
12684inherit(TouchInput, Input, {
12685 handler: function MTEhandler(ev) {
12686 var type = TOUCH_INPUT_MAP[ev.type];
12687 var touches = getTouches.call(this, ev, type);
12688 if (!touches) {
12689 return;
12690 }
12691
12692 this.callback(this.manager, type, {
12693 pointers: touches[0],
12694 changedPointers: touches[1],
12695 pointerType: INPUT_TYPE_TOUCH,
12696 srcEvent: ev
12697 });
12698 }
12699});
12700
12701/**
12702 * @this {TouchInput}
12703 * @param {Object} ev
12704 * @param {Number} type flag
12705 * @returns {undefined|Array} [all, changed]
12706 */
12707function getTouches(ev, type) {
12708 var allTouches = toArray(ev.touches);
12709 var targetIds = this.targetIds;
12710
12711 // when there is only one touch, the process can be simplified
12712 if (type & (INPUT_START | INPUT_MOVE) && allTouches.length === 1) {
12713 targetIds[allTouches[0].identifier] = true;
12714 return [allTouches, allTouches];
12715 }
12716
12717 var i,
12718 targetTouches,
12719 changedTouches = toArray(ev.changedTouches),
12720 changedTargetTouches = [],
12721 target = this.target;
12722
12723 // get target touches from touches
12724 targetTouches = allTouches.filter(function(touch) {
12725 return hasParent(touch.target, target);
12726 });
12727
12728 // collect touches
12729 if (type === INPUT_START) {
12730 i = 0;
12731 while (i < targetTouches.length) {
12732 targetIds[targetTouches[i].identifier] = true;
12733 i++;
12734 }
12735 }
12736
12737 // filter changed touches to only contain touches that exist in the collected target ids
12738 i = 0;
12739 while (i < changedTouches.length) {
12740 if (targetIds[changedTouches[i].identifier]) {
12741 changedTargetTouches.push(changedTouches[i]);
12742 }
12743
12744 // cleanup removed touches
12745 if (type & (INPUT_END | INPUT_CANCEL)) {
12746 delete targetIds[changedTouches[i].identifier];
12747 }
12748 i++;
12749 }
12750
12751 if (!changedTargetTouches.length) {
12752 return;
12753 }
12754
12755 return [
12756 // merge targetTouches with changedTargetTouches so it contains ALL touches, including 'end' and 'cancel'
12757 uniqueArray(targetTouches.concat(changedTargetTouches), 'identifier', true),
12758 changedTargetTouches
12759 ];
12760}
12761
12762/**
12763 * Combined touch and mouse input
12764 *
12765 * Touch has a higher priority then mouse, and while touching no mouse events are allowed.
12766 * This because touch devices also emit mouse events while doing a touch.
12767 *
12768 * @constructor
12769 * @extends Input
12770 */
12771
12772var DEDUP_TIMEOUT = 2500;
12773var DEDUP_DISTANCE = 25;
12774
12775function TouchMouseInput() {
12776 Input.apply(this, arguments);
12777
12778 var handler = bindFn(this.handler, this);
12779 this.touch = new TouchInput(this.manager, handler);
12780 this.mouse = new MouseInput(this.manager, handler);
12781
12782 this.primaryTouch = null;
12783 this.lastTouches = [];
12784}
12785
12786inherit(TouchMouseInput, Input, {
12787 /**
12788 * handle mouse and touch events
12789 * @param {NGHammer} manager
12790 * @param {String} inputEvent
12791 * @param {Object} inputData
12792 */
12793 handler: function TMEhandler(manager, inputEvent, inputData) {
12794 var isTouch = (inputData.pointerType == INPUT_TYPE_TOUCH),
12795 isMouse = (inputData.pointerType == INPUT_TYPE_MOUSE);
12796
12797 if (isMouse && inputData.sourceCapabilities && inputData.sourceCapabilities.firesTouchEvents) {
12798 return;
12799 }
12800
12801 // when we're in a touch event, record touches to de-dupe synthetic mouse event
12802 if (isTouch) {
12803 recordTouches.call(this, inputEvent, inputData);
12804 } else if (isMouse && isSyntheticEvent.call(this, inputData)) {
12805 return;
12806 }
12807
12808 this.callback(manager, inputEvent, inputData);
12809 },
12810
12811 /**
12812 * remove the event listeners
12813 */
12814 destroy: function destroy() {
12815 this.touch.destroy();
12816 this.mouse.destroy();
12817 }
12818});
12819
12820function recordTouches(eventType, eventData) {
12821 if (eventType & INPUT_START) {
12822 this.primaryTouch = eventData.changedPointers[0].identifier;
12823 setLastTouch.call(this, eventData);
12824 } else if (eventType & (INPUT_END | INPUT_CANCEL)) {
12825 setLastTouch.call(this, eventData);
12826 }
12827}
12828
12829function setLastTouch(eventData) {
12830 var touch = eventData.changedPointers[0];
12831
12832 if (touch.identifier === this.primaryTouch) {
12833 var lastTouch = {x: touch.clientX, y: touch.clientY};
12834 this.lastTouches.push(lastTouch);
12835 var lts = this.lastTouches;
12836 var removeLastTouch = function() {
12837 var i = lts.indexOf(lastTouch);
12838 if (i > -1) {
12839 lts.splice(i, 1);
12840 }
12841 };
12842 setTimeout(removeLastTouch, DEDUP_TIMEOUT);
12843 }
12844}
12845
12846function isSyntheticEvent(eventData) {
12847 var x = eventData.srcEvent.clientX, y = eventData.srcEvent.clientY;
12848 for (var i = 0; i < this.lastTouches.length; i++) {
12849 var t = this.lastTouches[i];
12850 var dx = Math.abs(x - t.x), dy = Math.abs(y - t.y);
12851 if (dx <= DEDUP_DISTANCE && dy <= DEDUP_DISTANCE) {
12852 return true;
12853 }
12854 }
12855 return false;
12856}
12857
12858var PREFIXED_TOUCH_ACTION = prefixed(TEST_ELEMENT.style, 'touchAction');
12859var NATIVE_TOUCH_ACTION = PREFIXED_TOUCH_ACTION !== undefined;
12860
12861// magical touchAction value
12862var TOUCH_ACTION_COMPUTE = 'compute';
12863var TOUCH_ACTION_AUTO = 'auto';
12864var TOUCH_ACTION_MANIPULATION = 'manipulation'; // not implemented
12865var TOUCH_ACTION_NONE = 'none';
12866var TOUCH_ACTION_PAN_X = 'pan-x';
12867var TOUCH_ACTION_PAN_Y = 'pan-y';
12868var TOUCH_ACTION_MAP = getTouchActionProps();
12869
12870/**
12871 * Touch Action
12872 * sets the touchAction property or uses the js alternative
12873 * @param {Manager} manager
12874 * @param {String} value
12875 * @constructor
12876 */
12877function TouchAction(manager, value) {
12878 this.manager = manager;
12879 this.set(value);
12880}
12881
12882TouchAction.prototype = {
12883 /**
12884 * set the touchAction value on the element or enable the polyfill
12885 * @param {String} value
12886 */
12887 set: function(value) {
12888 // find out the touch-action by the event handlers
12889 if (value == TOUCH_ACTION_COMPUTE) {
12890 value = this.compute();
12891 }
12892
12893 if (NATIVE_TOUCH_ACTION && this.manager.element.style && TOUCH_ACTION_MAP[value]) {
12894 this.manager.element.style[PREFIXED_TOUCH_ACTION] = value;
12895 }
12896 this.actions = value.toLowerCase().trim();
12897 },
12898
12899 /**
12900 * just re-set the touchAction value
12901 */
12902 update: function() {
12903 this.set(this.manager.options.touchAction);
12904 },
12905
12906 /**
12907 * compute the value for the touchAction property based on the recognizer's settings
12908 * @returns {String} value
12909 */
12910 compute: function() {
12911 var actions = [];
12912 each(this.manager.recognizers, function(recognizer) {
12913 if (boolOrFn(recognizer.options.enable, [recognizer])) {
12914 actions = actions.concat(recognizer.getTouchAction());
12915 }
12916 });
12917 return cleanTouchActions(actions.join(' '));
12918 },
12919
12920 /**
12921 * this method is called on each input cycle and provides the preventing of the browser behavior
12922 * @param {Object} input
12923 */
12924 preventDefaults: function(input) {
12925 var srcEvent = input.srcEvent;
12926 var direction = input.offsetDirection;
12927
12928 // if the touch action did prevented once this session
12929 if (this.manager.session.prevented) {
12930 srcEvent.preventDefault();
12931 return;
12932 }
12933
12934 var actions = this.actions;
12935 var hasNone = inStr(actions, TOUCH_ACTION_NONE) && !TOUCH_ACTION_MAP[TOUCH_ACTION_NONE];
12936 var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_Y];
12937 var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_X];
12938
12939 if (hasNone) {
12940 //do not prevent defaults if this is a tap gesture
12941
12942 var isTapPointer = input.pointers.length === 1;
12943 var isTapMovement = input.distance < 2;
12944 var isTapTouchTime = input.deltaTime < 250;
12945
12946 if (isTapPointer && isTapMovement && isTapTouchTime) {
12947 return;
12948 }
12949 }
12950
12951 if (hasPanX && hasPanY) {
12952 // `pan-x pan-y` means browser handles all scrolling/panning, do not prevent
12953 return;
12954 }
12955
12956 if (hasNone ||
12957 (hasPanY && direction & DIRECTION_HORIZONTAL) ||
12958 (hasPanX && direction & DIRECTION_VERTICAL)) {
12959 return this.preventSrc(srcEvent);
12960 }
12961 },
12962
12963 /**
12964 * call preventDefault to prevent the browser's default behavior (scrolling in most cases)
12965 * @param {Object} srcEvent
12966 */
12967 preventSrc: function(srcEvent) {
12968 this.manager.session.prevented = true;
12969 srcEvent.preventDefault();
12970 }
12971};
12972
12973/**
12974 * when the touchActions are collected they are not a valid value, so we need to clean things up. *
12975 * @param {String} actions
12976 * @returns {*}
12977 */
12978function cleanTouchActions(actions) {
12979 // none
12980 if (inStr(actions, TOUCH_ACTION_NONE)) {
12981 return TOUCH_ACTION_NONE;
12982 }
12983
12984 var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X);
12985 var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y);
12986
12987 // if both pan-x and pan-y are set (different recognizers
12988 // for different directions, e.g. horizontal pan but vertical swipe?)
12989 // we need none (as otherwise with pan-x pan-y combined none of these
12990 // recognizers will work, since the browser would handle all panning
12991 if (hasPanX && hasPanY) {
12992 return TOUCH_ACTION_NONE;
12993 }
12994
12995 // pan-x OR pan-y
12996 if (hasPanX || hasPanY) {
12997 return hasPanX ? TOUCH_ACTION_PAN_X : TOUCH_ACTION_PAN_Y;
12998 }
12999
13000 // manipulation
13001 if (inStr(actions, TOUCH_ACTION_MANIPULATION)) {
13002 return TOUCH_ACTION_MANIPULATION;
13003 }
13004
13005 return TOUCH_ACTION_AUTO;
13006}
13007
13008function getTouchActionProps() {
13009 if (!NATIVE_TOUCH_ACTION) {
13010 return false;
13011 }
13012 var touchMap = {};
13013 var cssSupports = window.CSS && window.CSS.supports;
13014 ['auto', 'manipulation', 'pan-y', 'pan-x', 'pan-x pan-y', 'none'].forEach(function(val) {
13015
13016 // If css.supports is not supported but there is native touch-action assume it supports
13017 // all values. This is the case for IE 10 and 11.
13018 touchMap[val] = cssSupports ? window.CSS.supports('touch-action', val) : true;
13019 });
13020 return touchMap;
13021}
13022
13023/**
13024 * Recognizer flow explained; *
13025 * All recognizers have the initial state of POSSIBLE when a input session starts.
13026 * The definition of a input session is from the first input until the last input, with all it's movement in it. *
13027 * Example session for mouse-input: mousedown -> mousemove -> mouseup
13028 *
13029 * On each recognizing cycle (see Manager.recognize) the .recognize() method is executed
13030 * which determines with state it should be.
13031 *
13032 * If the recognizer has the state FAILED, CANCELLED or RECOGNIZED (equals ENDED), it is reset to
13033 * POSSIBLE to give it another change on the next cycle.
13034 *
13035 * Possible
13036 * |
13037 * +-----+---------------+
13038 * | |
13039 * +-----+-----+ |
13040 * | | |
13041 * Failed Cancelled |
13042 * +-------+------+
13043 * | |
13044 * Recognized Began
13045 * |
13046 * Changed
13047 * |
13048 * Ended/Recognized
13049 */
13050var STATE_POSSIBLE = 1;
13051var STATE_BEGAN = 2;
13052var STATE_CHANGED = 4;
13053var STATE_ENDED = 8;
13054var STATE_RECOGNIZED = STATE_ENDED;
13055var STATE_CANCELLED = 16;
13056var STATE_FAILED = 32;
13057
13058/**
13059 * Recognizer
13060 * Every recognizer needs to extend from this class.
13061 * @constructor
13062 * @param {Object} options
13063 */
13064function Recognizer(options) {
13065 this.options = assign({}, this.defaults, options || {});
13066
13067 this.id = uniqueId();
13068
13069 this.manager = null;
13070
13071 // default is enable true
13072 this.options.enable = ifUndefined(this.options.enable, true);
13073
13074 this.state = STATE_POSSIBLE;
13075
13076 this.simultaneous = {};
13077 this.requireFail = [];
13078}
13079
13080Recognizer.prototype = {
13081 /**
13082 * @virtual
13083 * @type {Object}
13084 */
13085 defaults: {},
13086
13087 /**
13088 * set options
13089 * @param {Object} options
13090 * @return {Recognizer}
13091 */
13092 set: function(options) {
13093 assign(this.options, options);
13094
13095 // also update the touchAction, in case something changed about the directions/enabled state
13096 this.manager && this.manager.touchAction.update();
13097 return this;
13098 },
13099
13100 /**
13101 * recognize simultaneous with an other recognizer.
13102 * @param {Recognizer} otherRecognizer
13103 * @returns {Recognizer} this
13104 */
13105 recognizeWith: function(otherRecognizer) {
13106 if (invokeArrayArg(otherRecognizer, 'recognizeWith', this)) {
13107 return this;
13108 }
13109
13110 var simultaneous = this.simultaneous;
13111 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
13112 if (!simultaneous[otherRecognizer.id]) {
13113 simultaneous[otherRecognizer.id] = otherRecognizer;
13114 otherRecognizer.recognizeWith(this);
13115 }
13116 return this;
13117 },
13118
13119 /**
13120 * drop the simultaneous link. it doesnt remove the link on the other recognizer.
13121 * @param {Recognizer} otherRecognizer
13122 * @returns {Recognizer} this
13123 */
13124 dropRecognizeWith: function(otherRecognizer) {
13125 if (invokeArrayArg(otherRecognizer, 'dropRecognizeWith', this)) {
13126 return this;
13127 }
13128
13129 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
13130 delete this.simultaneous[otherRecognizer.id];
13131 return this;
13132 },
13133
13134 /**
13135 * recognizer can only run when an other is failing
13136 * @param {Recognizer} otherRecognizer
13137 * @returns {Recognizer} this
13138 */
13139 requireFailure: function(otherRecognizer) {
13140 if (invokeArrayArg(otherRecognizer, 'requireFailure', this)) {
13141 return this;
13142 }
13143
13144 var requireFail = this.requireFail;
13145 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
13146 if (inArray(requireFail, otherRecognizer) === -1) {
13147 requireFail.push(otherRecognizer);
13148 otherRecognizer.requireFailure(this);
13149 }
13150 return this;
13151 },
13152
13153 /**
13154 * drop the requireFailure link. it does not remove the link on the other recognizer.
13155 * @param {Recognizer} otherRecognizer
13156 * @returns {Recognizer} this
13157 */
13158 dropRequireFailure: function(otherRecognizer) {
13159 if (invokeArrayArg(otherRecognizer, 'dropRequireFailure', this)) {
13160 return this;
13161 }
13162
13163 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
13164 var index = inArray(this.requireFail, otherRecognizer);
13165 if (index > -1) {
13166 this.requireFail.splice(index, 1);
13167 }
13168 return this;
13169 },
13170
13171 /**
13172 * has require failures boolean
13173 * @returns {boolean}
13174 */
13175 hasRequireFailures: function() {
13176 return this.requireFail.length > 0;
13177 },
13178
13179 /**
13180 * if the recognizer can recognize simultaneous with an other recognizer
13181 * @param {Recognizer} otherRecognizer
13182 * @returns {Boolean}
13183 */
13184 canRecognizeWith: function(otherRecognizer) {
13185 return !!this.simultaneous[otherRecognizer.id];
13186 },
13187
13188 /**
13189 * You should use `tryEmit` instead of `emit` directly to check
13190 * that all the needed recognizers has failed before emitting.
13191 * @param {Object} input
13192 */
13193 emit: function(input) {
13194 var self = this;
13195 var state = this.state;
13196
13197 function emit(event) {
13198 self.manager.emit(event, input);
13199 }
13200
13201 // 'panstart' and 'panmove'
13202 if (state < STATE_ENDED) {
13203 emit(self.options.event + stateStr(state));
13204 }
13205
13206 emit(self.options.event); // simple 'eventName' events
13207
13208 if (input.additionalEvent) { // additional event(panleft, panright, pinchin, pinchout...)
13209 emit(input.additionalEvent);
13210 }
13211
13212 // panend and pancancel
13213 if (state >= STATE_ENDED) {
13214 emit(self.options.event + stateStr(state));
13215 }
13216 },
13217
13218 /**
13219 * Check that all the require failure recognizers has failed,
13220 * if true, it emits a gesture event,
13221 * otherwise, setup the state to FAILED.
13222 * @param {Object} input
13223 */
13224 tryEmit: function(input) {
13225 if (this.canEmit()) {
13226 return this.emit(input);
13227 }
13228 // it's failing anyway
13229 this.state = STATE_FAILED;
13230 },
13231
13232 /**
13233 * can we emit?
13234 * @returns {boolean}
13235 */
13236 canEmit: function() {
13237 var i = 0;
13238 while (i < this.requireFail.length) {
13239 if (!(this.requireFail[i].state & (STATE_FAILED | STATE_POSSIBLE))) {
13240 return false;
13241 }
13242 i++;
13243 }
13244 return true;
13245 },
13246
13247 /**
13248 * update the recognizer
13249 * @param {Object} inputData
13250 */
13251 recognize: function(inputData) {
13252 // make a new copy of the inputData
13253 // so we can change the inputData without messing up the other recognizers
13254 var inputDataClone = assign({}, inputData);
13255
13256 // is is enabled and allow recognizing?
13257 if (!boolOrFn(this.options.enable, [this, inputDataClone])) {
13258 this.reset();
13259 this.state = STATE_FAILED;
13260 return;
13261 }
13262
13263 // reset when we've reached the end
13264 if (this.state & (STATE_RECOGNIZED | STATE_CANCELLED | STATE_FAILED)) {
13265 this.state = STATE_POSSIBLE;
13266 }
13267
13268 this.state = this.process(inputDataClone);
13269
13270 // the recognizer has recognized a gesture
13271 // so trigger an event
13272 if (this.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED | STATE_CANCELLED)) {
13273 this.tryEmit(inputDataClone);
13274 }
13275 },
13276
13277 /**
13278 * return the state of the recognizer
13279 * the actual recognizing happens in this method
13280 * @virtual
13281 * @param {Object} inputData
13282 * @returns {Const} STATE
13283 */
13284 process: function(inputData) { }, // jshint ignore:line
13285
13286 /**
13287 * return the preferred touch-action
13288 * @virtual
13289 * @returns {Array}
13290 */
13291 getTouchAction: function() { },
13292
13293 /**
13294 * called when the gesture isn't allowed to recognize
13295 * like when another is being recognized or it is disabled
13296 * @virtual
13297 */
13298 reset: function() { }
13299};
13300
13301/**
13302 * get a usable string, used as event postfix
13303 * @param {Const} state
13304 * @returns {String} state
13305 */
13306function stateStr(state) {
13307 if (state & STATE_CANCELLED) {
13308 return 'cancel';
13309 } else if (state & STATE_ENDED) {
13310 return 'end';
13311 } else if (state & STATE_CHANGED) {
13312 return 'move';
13313 } else if (state & STATE_BEGAN) {
13314 return 'start';
13315 }
13316 return '';
13317}
13318
13319/**
13320 * direction cons to string
13321 * @param {Const} direction
13322 * @returns {String}
13323 */
13324function directionStr(direction) {
13325 if (direction == DIRECTION_DOWN) {
13326 return 'down';
13327 } else if (direction == DIRECTION_UP) {
13328 return 'up';
13329 } else if (direction == DIRECTION_LEFT) {
13330 return 'left';
13331 } else if (direction == DIRECTION_RIGHT) {
13332 return 'right';
13333 }
13334 return '';
13335}
13336
13337/**
13338 * get a recognizer by name if it is bound to a manager
13339 * @param {Recognizer|String} otherRecognizer
13340 * @param {Recognizer} recognizer
13341 * @returns {Recognizer}
13342 */
13343function getRecognizerByNameIfManager(otherRecognizer, recognizer) {
13344 var manager = recognizer.manager;
13345 if (manager) {
13346 return manager.get(otherRecognizer);
13347 }
13348 return otherRecognizer;
13349}
13350
13351/**
13352 * This recognizer is just used as a base for the simple attribute recognizers.
13353 * @constructor
13354 * @extends Recognizer
13355 */
13356function AttrRecognizer() {
13357 Recognizer.apply(this, arguments);
13358}
13359
13360inherit(AttrRecognizer, Recognizer, {
13361 /**
13362 * @namespace
13363 * @memberof AttrRecognizer
13364 */
13365 defaults: {
13366 /**
13367 * @type {Number}
13368 * @default 1
13369 */
13370 pointers: 1
13371 },
13372
13373 /**
13374 * Used to check if it the recognizer receives valid input, like input.distance > 10.
13375 * @memberof AttrRecognizer
13376 * @param {Object} input
13377 * @returns {Boolean} recognized
13378 */
13379 attrTest: function(input) {
13380 var optionPointers = this.options.pointers;
13381 return optionPointers === 0 || input.pointers.length === optionPointers;
13382 },
13383
13384 /**
13385 * Process the input and return the state for the recognizer
13386 * @memberof AttrRecognizer
13387 * @param {Object} input
13388 * @returns {*} State
13389 */
13390 process: function(input) {
13391 var state = this.state;
13392 var eventType = input.eventType;
13393
13394 var isRecognized = state & (STATE_BEGAN | STATE_CHANGED);
13395 var isValid = this.attrTest(input);
13396
13397 // on cancel input and we've recognized before, return STATE_CANCELLED
13398 if (isRecognized && (eventType & INPUT_CANCEL || !isValid)) {
13399 return state | STATE_CANCELLED;
13400 } else if (isRecognized || isValid) {
13401 if (eventType & INPUT_END) {
13402 return state | STATE_ENDED;
13403 } else if (!(state & STATE_BEGAN)) {
13404 return STATE_BEGAN;
13405 }
13406 return state | STATE_CHANGED;
13407 }
13408 return STATE_FAILED;
13409 }
13410});
13411
13412/**
13413 * Pan
13414 * Recognized when the pointer is down and moved in the allowed direction.
13415 * @constructor
13416 * @extends AttrRecognizer
13417 */
13418function PanRecognizer() {
13419 AttrRecognizer.apply(this, arguments);
13420
13421 this.pX = null;
13422 this.pY = null;
13423}
13424
13425inherit(PanRecognizer, AttrRecognizer, {
13426 /**
13427 * @namespace
13428 * @memberof PanRecognizer
13429 */
13430 defaults: {
13431 event: 'pan',
13432 threshold: 10,
13433 pointers: 1,
13434 direction: DIRECTION_ALL
13435 },
13436
13437 getTouchAction: function() {
13438 var direction = this.options.direction;
13439 var actions = [];
13440 if (direction & DIRECTION_HORIZONTAL) {
13441 actions.push(TOUCH_ACTION_PAN_Y);
13442 }
13443 if (direction & DIRECTION_VERTICAL) {
13444 actions.push(TOUCH_ACTION_PAN_X);
13445 }
13446 return actions;
13447 },
13448
13449 directionTest: function(input) {
13450 var options = this.options;
13451 var hasMoved = true;
13452 var distance = input.distance;
13453 var direction = input.direction;
13454 var x = input.deltaX;
13455 var y = input.deltaY;
13456
13457 // lock to axis?
13458 if (!(direction & options.direction)) {
13459 if (options.direction & DIRECTION_HORIZONTAL) {
13460 direction = (x === 0) ? DIRECTION_NONE : (x < 0) ? DIRECTION_LEFT : DIRECTION_RIGHT;
13461 hasMoved = x != this.pX;
13462 distance = Math.abs(input.deltaX);
13463 } else {
13464 direction = (y === 0) ? DIRECTION_NONE : (y < 0) ? DIRECTION_UP : DIRECTION_DOWN;
13465 hasMoved = y != this.pY;
13466 distance = Math.abs(input.deltaY);
13467 }
13468 }
13469 input.direction = direction;
13470 return hasMoved && distance > options.threshold && direction & options.direction;
13471 },
13472
13473 attrTest: function(input) {
13474 return AttrRecognizer.prototype.attrTest.call(this, input) &&
13475 (this.state & STATE_BEGAN || (!(this.state & STATE_BEGAN) && this.directionTest(input)));
13476 },
13477
13478 emit: function(input) {
13479
13480 this.pX = input.deltaX;
13481 this.pY = input.deltaY;
13482
13483 var direction = directionStr(input.direction);
13484
13485 if (direction) {
13486 input.additionalEvent = this.options.event + direction;
13487 }
13488 this._super.emit.call(this, input);
13489 }
13490});
13491
13492/**
13493 * Pinch
13494 * Recognized when two or more pointers are moving toward (zoom-in) or away from each other (zoom-out).
13495 * @constructor
13496 * @extends AttrRecognizer
13497 */
13498function PinchRecognizer() {
13499 AttrRecognizer.apply(this, arguments);
13500}
13501
13502inherit(PinchRecognizer, AttrRecognizer, {
13503 /**
13504 * @namespace
13505 * @memberof PinchRecognizer
13506 */
13507 defaults: {
13508 event: 'pinch',
13509 threshold: 0,
13510 pointers: 2
13511 },
13512
13513 getTouchAction: function() {
13514 return [TOUCH_ACTION_NONE];
13515 },
13516
13517 attrTest: function(input) {
13518 return this._super.attrTest.call(this, input) &&
13519 (Math.abs(input.scale - 1) > this.options.threshold || this.state & STATE_BEGAN);
13520 },
13521
13522 emit: function(input) {
13523 if (input.scale !== 1) {
13524 var inOut = input.scale < 1 ? 'in' : 'out';
13525 input.additionalEvent = this.options.event + inOut;
13526 }
13527 this._super.emit.call(this, input);
13528 }
13529});
13530
13531/**
13532 * Press
13533 * Recognized when the pointer is down for x ms without any movement.
13534 * @constructor
13535 * @extends Recognizer
13536 */
13537function PressRecognizer() {
13538 Recognizer.apply(this, arguments);
13539
13540 this._timer = null;
13541 this._input = null;
13542}
13543
13544inherit(PressRecognizer, Recognizer, {
13545 /**
13546 * @namespace
13547 * @memberof PressRecognizer
13548 */
13549 defaults: {
13550 event: 'press',
13551 pointers: 1,
13552 time: 251, // minimal time of the pointer to be pressed
13553 threshold: 9 // a minimal movement is ok, but keep it low
13554 },
13555
13556 getTouchAction: function() {
13557 return [TOUCH_ACTION_AUTO];
13558 },
13559
13560 process: function(input) {
13561 var options = this.options;
13562 var validPointers = input.pointers.length === options.pointers;
13563 var validMovement = input.distance < options.threshold;
13564 var validTime = input.deltaTime > options.time;
13565
13566 this._input = input;
13567
13568 // we only allow little movement
13569 // and we've reached an end event, so a tap is possible
13570 if (!validMovement || !validPointers || (input.eventType & (INPUT_END | INPUT_CANCEL) && !validTime)) {
13571 this.reset();
13572 } else if (input.eventType & INPUT_START) {
13573 this.reset();
13574 this._timer = setTimeoutContext(function() {
13575 this.state = STATE_RECOGNIZED;
13576 this.tryEmit();
13577 }, options.time, this);
13578 } else if (input.eventType & INPUT_END) {
13579 return STATE_RECOGNIZED;
13580 }
13581 return STATE_FAILED;
13582 },
13583
13584 reset: function() {
13585 clearTimeout(this._timer);
13586 },
13587
13588 emit: function(input) {
13589 if (this.state !== STATE_RECOGNIZED) {
13590 return;
13591 }
13592
13593 if (input && (input.eventType & INPUT_END)) {
13594 this.manager.emit(this.options.event + 'up', input);
13595 } else {
13596 this._input.timeStamp = now();
13597 this.manager.emit(this.options.event, this._input);
13598 }
13599 }
13600});
13601
13602/**
13603 * Rotate
13604 * Recognized when two or more pointer are moving in a circular motion.
13605 * @constructor
13606 * @extends AttrRecognizer
13607 */
13608function RotateRecognizer() {
13609 AttrRecognizer.apply(this, arguments);
13610}
13611
13612inherit(RotateRecognizer, AttrRecognizer, {
13613 /**
13614 * @namespace
13615 * @memberof RotateRecognizer
13616 */
13617 defaults: {
13618 event: 'rotate',
13619 threshold: 0,
13620 pointers: 2
13621 },
13622
13623 getTouchAction: function() {
13624 return [TOUCH_ACTION_NONE];
13625 },
13626
13627 attrTest: function(input) {
13628 return this._super.attrTest.call(this, input) &&
13629 (Math.abs(input.rotation) > this.options.threshold || this.state & STATE_BEGAN);
13630 }
13631});
13632
13633/**
13634 * Swipe
13635 * Recognized when the pointer is moving fast (velocity), with enough distance in the allowed direction.
13636 * @constructor
13637 * @extends AttrRecognizer
13638 */
13639function SwipeRecognizer() {
13640 AttrRecognizer.apply(this, arguments);
13641}
13642
13643inherit(SwipeRecognizer, AttrRecognizer, {
13644 /**
13645 * @namespace
13646 * @memberof SwipeRecognizer
13647 */
13648 defaults: {
13649 event: 'swipe',
13650 threshold: 10,
13651 velocity: 0.3,
13652 direction: DIRECTION_HORIZONTAL | DIRECTION_VERTICAL,
13653 pointers: 1
13654 },
13655
13656 getTouchAction: function() {
13657 return PanRecognizer.prototype.getTouchAction.call(this);
13658 },
13659
13660 attrTest: function(input) {
13661 var direction = this.options.direction;
13662 var velocity;
13663
13664 if (direction & (DIRECTION_HORIZONTAL | DIRECTION_VERTICAL)) {
13665 velocity = input.overallVelocity;
13666 } else if (direction & DIRECTION_HORIZONTAL) {
13667 velocity = input.overallVelocityX;
13668 } else if (direction & DIRECTION_VERTICAL) {
13669 velocity = input.overallVelocityY;
13670 }
13671
13672 return this._super.attrTest.call(this, input) &&
13673 direction & input.offsetDirection &&
13674 input.distance > this.options.threshold &&
13675 input.maxPointers == this.options.pointers &&
13676 abs(velocity) > this.options.velocity && input.eventType & INPUT_END;
13677 },
13678
13679 emit: function(input) {
13680 var direction = directionStr(input.offsetDirection);
13681 if (direction) {
13682 this.manager.emit(this.options.event + direction, input);
13683 }
13684
13685 this.manager.emit(this.options.event, input);
13686 }
13687});
13688
13689/**
13690 * A tap is ecognized when the pointer is doing a small tap/click. Multiple taps are recognized if they occur
13691 * between the given interval and position. The delay option can be used to recognize multi-taps without firing
13692 * a single tap.
13693 *
13694 * The eventData from the emitted event contains the property `tapCount`, which contains the amount of
13695 * multi-taps being recognized.
13696 * @constructor
13697 * @extends Recognizer
13698 */
13699function TapRecognizer() {
13700 Recognizer.apply(this, arguments);
13701
13702 // previous time and center,
13703 // used for tap counting
13704 this.pTime = false;
13705 this.pCenter = false;
13706
13707 this._timer = null;
13708 this._input = null;
13709 this.count = 0;
13710}
13711
13712inherit(TapRecognizer, Recognizer, {
13713 /**
13714 * @namespace
13715 * @memberof PinchRecognizer
13716 */
13717 defaults: {
13718 event: 'tap',
13719 pointers: 1,
13720 taps: 1,
13721 interval: 300, // max time between the multi-tap taps
13722 time: 250, // max time of the pointer to be down (like finger on the screen)
13723 threshold: 9, // a minimal movement is ok, but keep it low
13724 posThreshold: 10 // a multi-tap can be a bit off the initial position
13725 },
13726
13727 getTouchAction: function() {
13728 return [TOUCH_ACTION_MANIPULATION];
13729 },
13730
13731 process: function(input) {
13732 var options = this.options;
13733
13734 var validPointers = input.pointers.length === options.pointers;
13735 var validMovement = input.distance < options.threshold;
13736 var validTouchTime = input.deltaTime < options.time;
13737
13738 this.reset();
13739
13740 if ((input.eventType & INPUT_START) && (this.count === 0)) {
13741 return this.failTimeout();
13742 }
13743
13744 // we only allow little movement
13745 // and we've reached an end event, so a tap is possible
13746 if (validMovement && validTouchTime && validPointers) {
13747 if (input.eventType != INPUT_END) {
13748 return this.failTimeout();
13749 }
13750
13751 var validInterval = this.pTime ? (input.timeStamp - this.pTime < options.interval) : true;
13752 var validMultiTap = !this.pCenter || getDistance(this.pCenter, input.center) < options.posThreshold;
13753
13754 this.pTime = input.timeStamp;
13755 this.pCenter = input.center;
13756
13757 if (!validMultiTap || !validInterval) {
13758 this.count = 1;
13759 } else {
13760 this.count += 1;
13761 }
13762
13763 this._input = input;
13764
13765 // if tap count matches we have recognized it,
13766 // else it has began recognizing...
13767 var tapCount = this.count % options.taps;
13768 if (tapCount === 0) {
13769 // no failing requirements, immediately trigger the tap event
13770 // or wait as long as the multitap interval to trigger
13771 if (!this.hasRequireFailures()) {
13772 return STATE_RECOGNIZED;
13773 } else {
13774 this._timer = setTimeoutContext(function() {
13775 this.state = STATE_RECOGNIZED;
13776 this.tryEmit();
13777 }, options.interval, this);
13778 return STATE_BEGAN;
13779 }
13780 }
13781 }
13782 return STATE_FAILED;
13783 },
13784
13785 failTimeout: function() {
13786 this._timer = setTimeoutContext(function() {
13787 this.state = STATE_FAILED;
13788 }, this.options.interval, this);
13789 return STATE_FAILED;
13790 },
13791
13792 reset: function() {
13793 clearTimeout(this._timer);
13794 },
13795
13796 emit: function() {
13797 if (this.state == STATE_RECOGNIZED) {
13798 this._input.tapCount = this.count;
13799 this.manager.emit(this.options.event, this._input);
13800 }
13801 }
13802});
13803
13804/**
13805 * Simple way to create a manager with a default set of recognizers.
13806 * @param {HTMLElement} element
13807 * @param {Object} [options]
13808 * @constructor
13809 */
13810function NGHammer(element, options) {
13811 options = options || {};
13812 options.recognizers = ifUndefined(options.recognizers, NGHammer.defaults.preset);
13813 return new Manager(element, options);
13814}
13815
13816/**
13817 * @const {string}
13818 */
13819NGHammer.VERSION = '2.0.7';
13820
13821/**
13822 * default settings
13823 * @namespace
13824 */
13825NGHammer.defaults = {
13826 /**
13827 * set if DOM events are being triggered.
13828 * But this is slower and unused by simple implementations, so disabled by default.
13829 * @type {Boolean}
13830 * @default false
13831 */
13832 domEvents: false,
13833
13834 /**
13835 * The value for the touchAction property/fallback.
13836 * When set to `compute` it will magically set the correct value based on the added recognizers.
13837 * @type {String}
13838 * @default compute
13839 */
13840 touchAction: TOUCH_ACTION_COMPUTE,
13841
13842 /**
13843 * @type {Boolean}
13844 * @default true
13845 */
13846 enable: true,
13847
13848 /**
13849 * EXPERIMENTAL FEATURE -- can be removed/changed
13850 * Change the parent input target element.
13851 * If Null, then it is being set the to main element.
13852 * @type {Null|EventTarget}
13853 * @default null
13854 */
13855 inputTarget: null,
13856
13857 /**
13858 * force an input class
13859 * @type {Null|Function}
13860 * @default null
13861 */
13862 inputClass: null,
13863
13864 /**
13865 * Default recognizer setup when calling `NGHammer()`
13866 * When creating a new Manager these will be skipped.
13867 * @type {Array}
13868 */
13869 preset: [
13870 // RecognizerClass, options, [recognizeWith, ...], [requireFailure, ...]
13871 [RotateRecognizer, {enable: false}],
13872 [PinchRecognizer, {enable: false}, ['rotate']],
13873 [SwipeRecognizer, {direction: DIRECTION_HORIZONTAL}],
13874 [PanRecognizer, {direction: DIRECTION_HORIZONTAL}, ['swipe']],
13875 [TapRecognizer],
13876 [TapRecognizer, {event: 'doubletap', taps: 2}, ['tap']],
13877 [PressRecognizer]
13878 ],
13879
13880 /**
13881 * Some CSS properties can be used to improve the working of NGHammer.
13882 * Add them to this method and they will be set when creating a new Manager.
13883 * @namespace
13884 */
13885 cssProps: {
13886 /**
13887 * Disables text selection to improve the dragging gesture. Mainly for desktop browsers.
13888 * @type {String}
13889 * @default 'none'
13890 */
13891 userSelect: 'none',
13892
13893 /**
13894 * Disable the Windows Phone grippers when pressing an element.
13895 * @type {String}
13896 * @default 'none'
13897 */
13898 touchSelect: 'none',
13899
13900 /**
13901 * Disables the default callout shown when you touch and hold a touch target.
13902 * On iOS, when you touch and hold a touch target such as a link, Safari displays
13903 * a callout containing information about the link. This property allows you to disable that callout.
13904 * @type {String}
13905 * @default 'none'
13906 */
13907 touchCallout: 'none',
13908
13909 /**
13910 * Specifies whether zooming is enabled. Used by IE10>
13911 * @type {String}
13912 * @default 'none'
13913 */
13914 contentZooming: 'none',
13915
13916 /**
13917 * Specifies that an entire element should be draggable instead of its contents. Mainly for desktop browsers.
13918 * @type {String}
13919 * @default 'none'
13920 */
13921 userDrag: 'none',
13922
13923 /**
13924 * Overrides the highlight color shown when the user taps a link or a JavaScript
13925 * clickable element in iOS. This property obeys the alpha value, if specified.
13926 * @type {String}
13927 * @default 'rgba(0,0,0,0)'
13928 */
13929 tapHighlightColor: 'rgba(0,0,0,0)'
13930 }
13931};
13932
13933var STOP = 1;
13934var FORCED_STOP = 2;
13935
13936/**
13937 * Manager
13938 * @param {HTMLElement} element
13939 * @param {Object} [options]
13940 * @constructor
13941 */
13942function Manager(element, options) {
13943 this.options = assign({}, NGHammer.defaults, options || {});
13944
13945 this.options.inputTarget = this.options.inputTarget || element;
13946
13947 this.handlers = {};
13948 this.session = {};
13949 this.recognizers = [];
13950 this.oldCssProps = {};
13951
13952 this.element = element;
13953 this.input = createInputInstance(this);
13954 this.touchAction = new TouchAction(this, this.options.touchAction);
13955
13956 toggleCssProps(this, true);
13957
13958 each(this.options.recognizers, function(item) {
13959 var recognizer = this.add(new (item[0])(item[1]));
13960 item[2] && recognizer.recognizeWith(item[2]);
13961 item[3] && recognizer.requireFailure(item[3]);
13962 }, this);
13963}
13964
13965Manager.prototype = {
13966 /**
13967 * set options
13968 * @param {Object} options
13969 * @returns {Manager}
13970 */
13971 set: function(options) {
13972 assign(this.options, options);
13973
13974 // Options that need a little more setup
13975 if (options.touchAction) {
13976 this.touchAction.update();
13977 }
13978 if (options.inputTarget) {
13979 // Clean up existing event listeners and reinitialize
13980 this.input.destroy();
13981 this.input.target = options.inputTarget;
13982 this.input.init();
13983 }
13984 return this;
13985 },
13986
13987 /**
13988 * stop recognizing for this session.
13989 * This session will be discarded, when a new [input]start event is fired.
13990 * When forced, the recognizer cycle is stopped immediately.
13991 * @param {Boolean} [force]
13992 */
13993 stop: function(force) {
13994 this.session.stopped = force ? FORCED_STOP : STOP;
13995 },
13996
13997 /**
13998 * run the recognizers!
13999 * called by the inputHandler function on every movement of the pointers (touches)
14000 * it walks through all the recognizers and tries to detect the gesture that is being made
14001 * @param {Object} inputData
14002 */
14003 recognize: function(inputData) {
14004 var session = this.session;
14005 if (session.stopped) {
14006 return;
14007 }
14008
14009 // run the touch-action polyfill
14010 this.touchAction.preventDefaults(inputData);
14011
14012 var recognizer;
14013 var recognizers = this.recognizers;
14014
14015 // this holds the recognizer that is being recognized.
14016 // so the recognizer's state needs to be BEGAN, CHANGED, ENDED or RECOGNIZED
14017 // if no recognizer is detecting a thing, it is set to `null`
14018 var curRecognizer = session.curRecognizer;
14019
14020 // reset when the last recognizer is recognized
14021 // or when we're in a new session
14022 if (!curRecognizer || (curRecognizer && curRecognizer.state & STATE_RECOGNIZED)) {
14023 curRecognizer = session.curRecognizer = null;
14024 }
14025
14026 var i = 0;
14027 while (i < recognizers.length) {
14028 recognizer = recognizers[i];
14029
14030 // find out if we are allowed try to recognize the input for this one.
14031 // 1. allow if the session is NOT forced stopped (see the .stop() method)
14032 // 2. allow if we still haven't recognized a gesture in this session, or the this recognizer is the one
14033 // that is being recognized.
14034 // 3. allow if the recognizer is allowed to run simultaneous with the current recognized recognizer.
14035 // this can be setup with the `recognizeWith()` method on the recognizer.
14036 if (session.stopped !== FORCED_STOP && ( // 1
14037 !curRecognizer || recognizer == curRecognizer || // 2
14038 recognizer.canRecognizeWith(curRecognizer))) { // 3
14039 recognizer.recognize(inputData);
14040 } else {
14041 recognizer.reset();
14042 }
14043
14044 // if the recognizer has been recognizing the input as a valid gesture, we want to store this one as the
14045 // current active recognizer. but only if we don't already have an active recognizer
14046 if (!curRecognizer && recognizer.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED)) {
14047 curRecognizer = session.curRecognizer = recognizer;
14048 }
14049 i++;
14050 }
14051 },
14052
14053 /**
14054 * get a recognizer by its event name.
14055 * @param {Recognizer|String} recognizer
14056 * @returns {Recognizer|Null}
14057 */
14058 get: function(recognizer) {
14059 if (recognizer instanceof Recognizer) {
14060 return recognizer;
14061 }
14062
14063 var recognizers = this.recognizers;
14064 for (var i = 0; i < recognizers.length; i++) {
14065 if (recognizers[i].options.event == recognizer) {
14066 return recognizers[i];
14067 }
14068 }
14069 return null;
14070 },
14071
14072 /**
14073 * add a recognizer to the manager
14074 * existing recognizers with the same event name will be removed
14075 * @param {Recognizer} recognizer
14076 * @returns {Recognizer|Manager}
14077 */
14078 add: function(recognizer) {
14079 if (invokeArrayArg(recognizer, 'add', this)) {
14080 return this;
14081 }
14082
14083 // remove existing
14084 var existing = this.get(recognizer.options.event);
14085 if (existing) {
14086 this.remove(existing);
14087 }
14088
14089 this.recognizers.push(recognizer);
14090 recognizer.manager = this;
14091
14092 this.touchAction.update();
14093 return recognizer;
14094 },
14095
14096 /**
14097 * remove a recognizer by name or instance
14098 * @param {Recognizer|String} recognizer
14099 * @returns {Manager}
14100 */
14101 remove: function(recognizer) {
14102 if (invokeArrayArg(recognizer, 'remove', this)) {
14103 return this;
14104 }
14105
14106 recognizer = this.get(recognizer);
14107
14108 // let's make sure this recognizer exists
14109 if (recognizer) {
14110 var recognizers = this.recognizers;
14111 var index = inArray(recognizers, recognizer);
14112
14113 if (index !== -1) {
14114 recognizers.splice(index, 1);
14115 this.touchAction.update();
14116 }
14117 }
14118
14119 return this;
14120 },
14121
14122 /**
14123 * bind event
14124 * @param {String} events
14125 * @param {Function} handler
14126 * @returns {EventEmitter} this
14127 */
14128 on: function(events, handler) {
14129 if (events === undefined) {
14130 return;
14131 }
14132 if (handler === undefined) {
14133 return;
14134 }
14135
14136 var handlers = this.handlers;
14137 each(splitStr(events), function(event) {
14138 handlers[event] = handlers[event] || [];
14139 handlers[event].push(handler);
14140 });
14141 return this;
14142 },
14143
14144 /**
14145 * unbind event, leave emit blank to remove all handlers
14146 * @param {String} events
14147 * @param {Function} [handler]
14148 * @returns {EventEmitter} this
14149 */
14150 off: function(events, handler) {
14151 if (events === undefined) {
14152 return;
14153 }
14154
14155 var handlers = this.handlers;
14156 each(splitStr(events), function(event) {
14157 if (!handler) {
14158 delete handlers[event];
14159 } else {
14160 handlers[event] && handlers[event].splice(inArray(handlers[event], handler), 1);
14161 }
14162 });
14163 return this;
14164 },
14165
14166 /**
14167 * emit event to the listeners
14168 * @param {String} event
14169 * @param {Object} data
14170 */
14171 emit: function(event, data) {
14172 // we also want to trigger dom events
14173 if (this.options.domEvents) {
14174 triggerDomEvent(event, data);
14175 }
14176
14177 // no handlers, so skip it all
14178 var handlers = this.handlers[event] && this.handlers[event].slice();
14179 if (!handlers || !handlers.length) {
14180 return;
14181 }
14182
14183 data.type = event;
14184 data.preventDefault = function() {
14185 data.srcEvent.preventDefault();
14186 };
14187
14188 var i = 0;
14189 while (i < handlers.length) {
14190 handlers[i](data);
14191 i++;
14192 }
14193 },
14194
14195 /**
14196 * destroy the manager and unbinds all events
14197 * it doesn't unbind dom events, that is the user own responsibility
14198 */
14199 destroy: function() {
14200 this.element && toggleCssProps(this, false);
14201
14202 this.handlers = {};
14203 this.session = {};
14204 this.input.destroy();
14205 this.element = null;
14206 }
14207};
14208
14209/**
14210 * add/remove the css properties as defined in manager.options.cssProps
14211 * @param {Manager} manager
14212 * @param {Boolean} add
14213 */
14214function toggleCssProps(manager, add) {
14215 var element = manager.element;
14216 if (!element.style) {
14217 return;
14218 }
14219 var prop;
14220 each(manager.options.cssProps, function(value, name) {
14221 prop = prefixed(element.style, name);
14222 if (add) {
14223 manager.oldCssProps[prop] = element.style[prop];
14224 element.style[prop] = value;
14225 } else {
14226 element.style[prop] = manager.oldCssProps[prop] || '';
14227 }
14228 });
14229 if (!add) {
14230 manager.oldCssProps = {};
14231 }
14232}
14233
14234/**
14235 * trigger dom event
14236 * @param {String} event
14237 * @param {Object} data
14238 */
14239function triggerDomEvent(event, data) {
14240 var gestureEvent = document.createEvent('Event');
14241 gestureEvent.initEvent(event, true, true);
14242 gestureEvent.gesture = data;
14243 data.target.dispatchEvent(gestureEvent);
14244}
14245
14246assign(NGHammer, {
14247 INPUT_START: INPUT_START,
14248 INPUT_MOVE: INPUT_MOVE,
14249 INPUT_END: INPUT_END,
14250 INPUT_CANCEL: INPUT_CANCEL,
14251
14252 STATE_POSSIBLE: STATE_POSSIBLE,
14253 STATE_BEGAN: STATE_BEGAN,
14254 STATE_CHANGED: STATE_CHANGED,
14255 STATE_ENDED: STATE_ENDED,
14256 STATE_RECOGNIZED: STATE_RECOGNIZED,
14257 STATE_CANCELLED: STATE_CANCELLED,
14258 STATE_FAILED: STATE_FAILED,
14259
14260 DIRECTION_NONE: DIRECTION_NONE,
14261 DIRECTION_LEFT: DIRECTION_LEFT,
14262 DIRECTION_RIGHT: DIRECTION_RIGHT,
14263 DIRECTION_UP: DIRECTION_UP,
14264 DIRECTION_DOWN: DIRECTION_DOWN,
14265 DIRECTION_HORIZONTAL: DIRECTION_HORIZONTAL,
14266 DIRECTION_VERTICAL: DIRECTION_VERTICAL,
14267 DIRECTION_ALL: DIRECTION_ALL,
14268
14269 Manager: Manager,
14270 Input: Input,
14271 TouchAction: TouchAction,
14272
14273 TouchInput: TouchInput,
14274 MouseInput: MouseInput,
14275 PointerEventInput: PointerEventInput,
14276 TouchMouseInput: TouchMouseInput,
14277 SingleTouchInput: SingleTouchInput,
14278
14279 Recognizer: Recognizer,
14280 AttrRecognizer: AttrRecognizer,
14281 Tap: TapRecognizer,
14282 Pan: PanRecognizer,
14283 Swipe: SwipeRecognizer,
14284 Pinch: PinchRecognizer,
14285 Rotate: RotateRecognizer,
14286 Press: PressRecognizer,
14287
14288 on: addEventListeners,
14289 off: removeEventListeners,
14290 each: each,
14291 merge: merge,
14292 extend: extend,
14293 assign: assign,
14294 inherit: inherit,
14295 bindFn: bindFn,
14296 prefixed: prefixed
14297});
14298
14299// this prevents errors when NGHammer is loaded in the presence of an AMD
14300// style loader but by script tag, not by the loader.
14301var freeGlobal = (typeof window !== 'undefined' ? window : (typeof self !== 'undefined' ? self : {})); // jshint ignore:line
14302freeGlobal.NGHammer = NGHammer;
14303
14304if (typeof define === 'function' && define.amdDISABLED) {
14305 define(function() {
14306 return NGHammer;
14307 });
14308} else if (typeof module != 'undefined' && module.exports) {
14309 module.exports = NGHammer;
14310} else {
14311 window[exportName] = NGHammer;
14312}
14313
14314})(window, document, 'NGHammer');
14315
14316
14317
14318
14319
14320
14321// END NANOGALLERY2
14322// }( jQuery )));
14323}));
14324
14325
14326//##########################################################################################################################
14327//##########################################################################################################################
14328//##########################################################################################################################
14329//##########################################################################################################################
14330//##########################################################################################################################
14331
14332// nanogallery2 auto start whithout javascript call
14333(function(){
14334 'use strict';
14335 jQuery(document).ready(function () {
14336
14337 // var t=document.querySelectorAll('[data-nanogallery2-portable]');
14338 // if( t.length > 0 ) {
14339 // portable mode
14340 // var link = document.createElement('link');
14341 // link.setAttribute("rel", "stylesheet");
14342 // link.setAttribute("type", "text/css");
14343 // link.onload = function(){
14344 // for( var i=0; i < t.length; i++ ) {
14345 // jQuery(t[i]).nanogallery2(jQuery(t[i]).data('nanogallery2-portable'));
14346 // }
14347 // }
14348 // link.setAttribute("href", '//nano.gallery/css/nanogallery2.css');
14349 // document.getElementsByTagName("head")[0].appendChild(link);
14350 // }
14351 // else {
14352 // standard mode
14353 var t=document.querySelectorAll('[data-nanogallery2]');
14354 for( var i=0; i < t.length; i++ ) {
14355 jQuery(t[i]).nanogallery2(jQuery(t[i]).data('nanogallery2'));
14356 }
14357 // }
14358
14359 });
14360}).call(null);
14361