UNPKG

584 kBJavaScriptView Raw
1/* nanogallery2 - v2.4.2 - 2019-03-22 - https://nanogallery2.nanostudio.org */
2/**!
3 * @preserve nanogallery2 - javascript photo / video gallery and lightbox
4 * Homepage: http://nanogallery2.nanostudio.org
5 * Sources: https://github.com/nanostudio-org/nanogallery2
6 *
7 * License: GPLv3 and commercial licence
8 *
9 * Requirements:
10 * - jQuery (http://www.jquery.com) - version >= 1.7.1
11 *
12 * Embeded components:
13 * - shifty (https://github.com/jeremyckahn/shifty)
14 * - imagesloaded (https://github.com/desandro/imagesloaded)
15 * - hammer.js (http://hammerjs.github.io/)
16 * - screenfull.js (https://github.com/sindresorhus/screenfull.js)
17 * Tools:
18 * - webfont generated with http://fontello.com - mainly based on Font Awesome Copyright (C) 2012 by Dave Gandy (http://fontawesome.io/)
19 * - ICO online converter: https://iconverticons.com/online/
20 */
21
22
23
24// ###########################################
25// ##### nanogallery2 as a JQUERY PLUGIN #####
26// ###########################################
27
28
29// Expose plugin as an AMD module if AMD loader is present:
30(function (factory) {
31 "use strict";
32 if (typeof define === 'function' && define.amd) {
33 // AMD. Register as an anonymous module.
34 // define('nanogallery2', ['jquery'], factory);
35 define(['jquery'], factory);
36 } else if (typeof exports === 'object' && typeof require === 'function') {
37 // Browserify
38 factory(require('jquery'));
39 } else {
40 // Browser globals
41 factory(jQuery);
42 }
43}(function ($) {
44// ;(function ($) {
45 "use strict";
46
47 //##### TOOLS/HELPERS ####
48
49 // Convert color to RGB/RGBA
50 function ColorHelperToRGB( color ) {
51 var obj = document.getElementById('ngyColorHelperToRGB');
52 if (obj === null) {
53 obj = document.createElement('div');
54 obj.id = "ngyColorHelperToRGB";
55 obj.style.cssText = 'display: none; color:' + color + ';';
56 document.body.appendChild(obj);
57 }
58
59 var rgb = getComputedStyle(obj).color;
60
61 // to get HEX value:
62 // var rgb = getComputedStyle(obj).color.match(/\d+/g);
63 // var r = parseInt(rgb[0]).toString(16);
64 // var g = parseInt(rgb[1]).toString(16);
65 // var b = parseInt(rgb[2]).toString(16);
66 // var hex = '#' + r + g + b;
67
68 return rgb;
69 }
70
71
72 // ##### helper for color handling
73 // - normalise RGB/RGBA/HEX format
74 // - lighten/darken color
75 // Inspired by:
76 // https://stackoverflow.com/questions/5560248/programmatically-lighten-or-darken-a-hex-color-or-rgb-and-blend-colors
77 // http://www.pimptrizkit.com/?t=20%20Shades
78 function ShadeBlendConvert (p, from, to) {
79 var rgba='';
80 if( from.toUpperCase().substring(0,5) == 'RGBA(' ) {
81 rgba='a';
82 from='rgb('+from.substring(5);
83 }
84
85 if(typeof(p)!="number"||p<-1||p>1||typeof(from)!="string"||(from[0]!='r'&&from[0]!='#')||(typeof(to)!="string"&&typeof(to)!="undefined"))return null;
86 //if(!this.sbcRip)this.sbcRip=function(d){
87 function sbcRip(d){
88 var l=d.length,RGB=new Object();
89 if(l>9){
90 d=d.split(",");
91 if(d.length<3||d.length>4)return null;
92 RGB[0]=i(d[0].slice(4)),RGB[1]=i(d[1]),RGB[2]=i(d[2]),RGB[3]=d[3]?parseFloat(d[3]):-1;
93 }else{
94 if(l==8||l==6||l<4)return null;
95 if(l<6)d="#"+d[1]+d[1]+d[2]+d[2]+d[3]+d[3]+(l>4?d[4]+""+d[4]:"");
96 d=i(d.slice(1),16),RGB[0]=d>>16&255,RGB[1]=d>>8&255,RGB[2]=d&255,RGB[3]=l==9||l==5?r(((d>>24&255)/255)*10000)/10000:-1;
97 }
98 return RGB;
99 }
100 var i=parseInt,r=Math.round,h=from.length>9,h=typeof(to)=="string"?to.length>9?true:to=="c"?!h:false:h,b=p<0,p=b?p*-1:p,to=to&&to!="c"?to:b?"#000000":"#FFFFFF",f=sbcRip(from),t=sbcRip(to);
101 if(!f||!t)return null;
102 if(h)return "rgb"+rgba+"("+r((t[0]-f[0])*p+f[0])+","+r((t[1]-f[1])*p+f[1])+","+r((t[2]-f[2])*p+f[2])+(f[3]<0&&t[3]<0?")":","+(f[3]>-1&&t[3]>-1?r(((t[3]-f[3])*p+f[3])*10000)/10000:t[3]<0?f[3]:t[3])+")");
103 else return "#"+(0x100000000+(f[3]>-1&&t[3]>-1?r(((t[3]-f[3])*p+f[3])*255):t[3]>-1?r(t[3]*255):f[3]>-1?r(f[3]*255):255)*0x1000000+r((t[0]-f[0])*p+f[0])*0x10000+r((t[1]-f[1])*p+f[1])*0x100+r((t[2]-f[2])*p+f[2])).toString(16).slice(f[3]>-1||t[3]>-1?1:3);
104 }
105
106
107 // ##### clone a javascript object
108 function cloneJSObject( obj ) {
109 if (obj === null || typeof obj !== 'object') {
110 return obj;
111 }
112
113 var temp = obj.constructor(); // give temp the original obj's constructor
114 for (var key in obj) {
115 temp[key] = cloneJSObject(obj[key]);
116 }
117 return temp;
118 }
119
120 // get viewport coordinates and size
121 function getViewport() {
122 var $win = jQuery(window);
123 return {
124 l: $win.scrollLeft(),
125 t: $win.scrollTop(),
126 w: $win.width(),
127 h: $win.height()
128 }
129 }
130
131
132 // avoid if possible (performance issue)
133 function inViewport( $elt, threshold ) {
134 var wp = getViewport(),
135 eltOS = $elt.offset(),
136 th = $elt.outerHeight(true),
137 tw = $elt.outerWidth(true);
138 if( eltOS.top >= (wp.t - threshold)
139 && (eltOS.top + th) <= (wp.t + wp.h + threshold)
140 && eltOS.left >= (wp.l - threshold)
141 && (eltOS.left + tw) <= (wp.l + wp.w + threshold) ) {
142 return true;
143 }
144 else {
145 return false;
146 }
147 }
148
149 // avoid if possible (performance issue)
150 function inViewportVert( $elt, threshold ) {
151 var wp = getViewport(),
152 eltOS = $elt.offset(),
153 th = $elt.outerHeight(true);
154 //var tw=$elt.outerWidth(true);
155
156 if( wp.t == 0 && (eltOS.top) <= (wp.t + wp.h ) ) { return true; }
157
158 if( eltOS.top >= (wp.t)
159 && (eltOS.top + th) <= (wp.t + wp.h - threshold) ) {
160 return true;
161 }
162 else {
163 return false;
164 }
165 }
166
167
168 // set z-index to display 2 elements on top of all others
169 function set2ElementsOnTop( start, elt1, elt2 ) {
170 var highest_index = 0;
171 if( start=='' ) { start= '*'; }
172 jQuery(start).each(function() {
173 var cur = parseInt(jQuery(this).css('z-index'));
174 highest_index = cur > highest_index ? cur : highest_index;
175 });
176 highest_index++;
177 jQuery(elt2).css('z-index',highest_index+1);
178 jQuery(elt1).css('z-index',highest_index);
179 }
180
181 // set z-index to display element on top of all others
182 function setElementOnTop( start, elt ) {
183 var highest_index = 0;
184 if( start == '' ) { start = '*'; }
185 jQuery(start).each(function() {
186 var cur = parseInt(jQuery(this).css('z-index'));
187 highest_index = cur > highest_index ? cur : highest_index;
188 });
189 highest_index++;
190 jQuery(elt).css('z-index',highest_index);
191 }
192
193 // return the real type of the object
194 var toType = function( obj ) {
195 // by Angus Croll - http://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator/
196 return ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase()
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 return true;
255 }
256 };
257
258
259 /** @function nanoAlert */
260 /* Display an alert message in a specific element */
261 NGY2Tools.NanoAlert = function(context, msg, verbose) {
262 NGY2Tools.NanoConsoleLog.call(context, msg);
263 if( context.$E.conConsole != null ) {
264 context.$E.conConsole.css({visibility: 'visible', minHeight: '100px'});
265 if( verbose == false ) {
266 context.$E.conConsole.append('<p>' + msg + '</p>');
267 }
268 else {
269 context.$E.conConsole.append('<p>nanogallery2: '+ msg + ' [' + context.baseEltID + ']</p>');
270 }
271 }
272 };
273
274
275 /** @function NanoConsoleLog */
276 /* write message to the browser console */
277 NGY2Tools.NanoConsoleLog = function(context, msg) {
278 if (window.console) { console.log('nanogallery2: ' + msg + ' [' + context.baseEltID + ']'); }
279 };
280
281
282 /** @function PreloaderDisplay() */
283 /* Display/hide preloader */
284 NGY2Tools.PreloaderDisplay = function(display) {
285 if( display === true ) {
286 this.$E.conLoadingB.removeClass('nanoGalleryLBarOff').addClass('nanoGalleryLBar');
287 }
288 else {
289 this.$E.conLoadingB.removeClass('nanoGalleryLBar').addClass('nanoGalleryLBarOff');
290 }
291 };
292
293 //+ Jonas Raoni Soares Silva
294 //@ http://jsfromhell.com/array/shuffle [v1.0]
295 NGY2Tools.AreaShuffle = function (o) {
296 for (var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
297 return o;
298 };
299
300 /** @function GetImageTitleFromURL() */
301 /* retrieve filemane */
302 NGY2Tools.GetImageTitleFromURL = function( imageURL ) {
303 if( this.O.thumbnailLabel.get('title') == '%filename' ) {
304 return (imageURL.split('/').pop()).replace('_',' ');
305 }
306
307 if( this.O.thumbnailLabel.get('title') == '%filenameNoExt' ) {
308 var s=imageURL.split('/').pop();
309 return (s.split('.').shift()).replace('_',' ');
310 }
311 // return imageURL;
312 return '';
313 };
314
315
316 /** @function AlbumPostProcess() */
317 /* post process one album based on plugin general parameters --> sorting/maxItems*/
318 NGY2Tools.AlbumPostProcess = function(albumID) {
319
320 // this function can probably be optimized....
321
322 var sortOrder = this.gallerySorting[this.GOM.curNavLevel];
323 var maxItems = this.galleryMaxItems[this.GOM.curNavLevel];
324
325 if( sortOrder != '' || maxItems > 0 ) {
326
327 // copy album's items to a new array
328 var currentAlbum = this.I.filter( function( obj ) {
329 return( obj.albumID == albumID && obj.kind != 'albumUp' );
330 });
331
332 // sorting options
333 switch( sortOrder ) {
334 case 'RANDOM':
335 currentAlbum = NGY2Tools.AreaShuffle(currentAlbum);
336 break;
337 case 'REVERSED':
338 currentAlbum = currentAlbum.reverse();
339 break;
340 case 'TITLEASC':
341 currentAlbum.sort(function (a, b) {
342 return( (a.title.toUpperCase() < b.title.toUpperCase()) ? -1 : ((a.title.toUpperCase() > b.title.toUpperCase()) ? 1 : 0) );
343 });
344 break;
345 case 'TITLEDESC':
346 currentAlbum.sort(function (a, b) {
347 return( (a.title.toUpperCase() > b.title.toUpperCase()) ? -1 : ((a.title.toUpperCase() < b.title.toUpperCase()) ? 1 : 0) );
348 });
349 break;
350 }
351
352 // max Items
353 if( maxItems > 0 && currentAlbum.length > maxItems ) {
354 currentAlbum.splice(maxItems - 1, currentAlbum.length-maxItems );
355 }
356
357 // remove the albums's items from the global items array
358 this.I.removeIf( function( obj ) {
359 return( obj.albumID == albumID && obj.kind != 'albumUp' );
360 });
361
362 // add the sorted items back to the album
363 this.I.push.apply(this.I, currentAlbum);
364
365 }
366 };
367
368 return NGY2Tools;
369 })();
370
371 // ====================
372 // ===== NGY2Item =====
373 // ====================
374 window.NGY2Item = (function() {
375 var nextId = 1; // private static --> all instances
376
377 // constructor
378 function NGY2Item( itemID ) {
379 //window.NGY2Item = function( itemID ) {
380 var ID = 0; // private
381
382 // public (this instance only)
383 if( itemID === undefined || itemID === null ) {
384 ID = nextId++;
385 }
386 else {
387 ID = itemID;
388 }
389 this.GetID = function () { return ID; };
390
391 // public
392 this.kind = ''; // 'image', 'album' or 'albumUp'
393 this.mediaKind = 'img'; // 'img', 'iframe'
394 this.mediaMarkup = '';
395 this.G = null; // pointer to global instance
396 this.title = ''; // image title
397 this.description = ''; // image description
398 this.albumID = 0; // ID of the parent album
399 this.src = ''; // full sized image URL
400 this.width = 0; // image width
401 this.height = 0; // image height
402 this.destinationURL = ''; // thumbnail destination URL --> open URL instead of displaying image
403 this.downloadURL = ''; // thumbnail download URL --> specify the image for download button
404 this.author = ''; // image/album author
405 this.left= 0; // store position to animate from old to new
406 this.top= 0;
407 this.width= 0; // store size to avoid setting width/height if not required
408 this.height= 0;
409 this.resizedContentWidth= 0; // store size of content (image) to avoid setting width/height if not required
410 this.resizedContentHeight= 0;
411 this.thumbs = { // URLs and sizes for user defined
412 url: { l1: { xs: '', sm:'', me: '', la: '', xl: '' }, lN: { xs: '', sm: '', me: '', la:'', xl: '' } },
413 width: { l1: { xs: 0, sm: 0, me: 0, la: 0 , xl: 0 }, lN: { xs: 0 , sm: 0, me: 0, la: 0, xl: 0 } },
414 height: { l1: { xs: 0, sm: 0, me: 0, la: 0 , xl: 0 }, lN: { xs: 0, sm: 0, me: 0, la: 0, xl: 0 } }
415 };
416 this.thumbnailImgRevealed = false; // thumbnail image already revealed
417 this.imageDominantColors = null; // base64 GIF
418 this.imageDominantColor = null; // HEX RGB
419 this.featured = false; // featured element
420 this.flickrThumbSizes = {}; // store URLs for all available thumbnail sizes (flickr)
421 this.picasaThumbs = null; // store URLs and sizes
422 this.hovered = false; // is the thumbnail currently hovered?
423 this.hoverInitDone = false;
424 this.contentIsLoaded = false; // album: are items already loaded?
425 this.contentLength = 0; // album: number of items (real number of items in memory)
426 this.numberItems = 0; // album: number of items (value returned by data source)
427 this.mediaNumber = 0; // media number in the album
428 this.imageCounter = 0; // number of images in an album
429 this.eltTransform = []; // store the CSS transformations
430 this.eltFilter = []; // store the CSS filters
431 this.eltEffect = []; // store data about hover effects animations
432 this.authkey = ''; // for Google Photos private (hidden) albums
433 this.paginationLastPage = 0; // for albums
434 this.paginationLastWidth = 0; // for albums
435 this.customData = {};
436 this.selected = false;
437 this.imageWidth = 0; // image natural (real) width
438 this.imageHeight = 0; // image natural (real) height
439 this.$elt = null; // pointer to the corresponding DOM element
440 this.$Elts = []; // cached pointers to the thumbnail content -> to avoid jQuery().find()
441 this.tags = []; // list of tags of the current item
442 this.albumTagList = []; // list of all the tags of the items contained in the current album
443 this.albumTagListSel = []; // list of currently selected tags (only for albums)
444 this.exif = { exposure: '', flash: '', focallength: '', fstop: '', iso: '', model: '', time: '', location: ''};
445 this.deleted = false; // item is deleted -> do not display anymore
446 this.rotationAngle = 0; // image display rotation angle
447 }
448
449 // public static
450
451 NGY2Item.Get = function( instance, ID ) {
452 var l = instance.I.length;
453 for( var i = 0; i < l; i++ ) {
454 if( instance.I[i].GetID() == ID ) {
455 return instance.I[i];
456 }
457 }
458 return null;
459 };
460
461 NGY2Item.GetIdx = function( instance, ID ) {
462 var l = instance.I.length;
463 for( var i = 0; i < l; i++ ) {
464 if( instance.I[i].GetID() == ID ) {
465 return i;
466 }
467 }
468 return -1;
469 };
470
471 // create new item (image, album or albumUp)
472 NGY2Item.New = function( instance, title, description, ID, albumID, kind, tags ) {
473 var album = NGY2Item.Get( instance, albumID );
474
475 if( albumID != -1 && albumID != 0 && title !='image gallery by nanogallery2 [build]' ) {
476 if( instance.O.thumbnailLevelUp && album.getContentLength(false) == 0 && instance.O.album == '' ) {
477 // add navigation thumbnail (album up)
478 var item = new NGY2Item('0');
479 instance.I.push( item );
480 album.contentLength += 1;
481 item.title = 'UP';
482 item.albumID = albumID;
483 item.kind = 'albumUp';
484 item.G = instance;
485
486 jQuery.extend( true, item.thumbs.width, instance.tn.defaultSize.width);
487 jQuery.extend( true, item.thumbs.height, instance.tn.defaultSize.height);
488 }
489 }
490
491 var item = NGY2Item.Get(instance, ID);
492 if( item === null ){
493 // create a new item (otherwise, just update the existing one)
494 item = new NGY2Item(ID);
495 instance.I.push(item);
496 if( albumID != -1 && title !='image gallery by nanogallery2 [build]' ) {
497 album.contentLength+=1;
498 }
499 }
500 item.G = instance;
501
502 item.albumID = albumID;
503 item.kind = kind;
504 if( kind == 'image' ) {
505 album.imageCounter += 1;
506 item.mediaNumber = album.imageCounter;
507 }
508
509 // check keyword to find features images/albums
510 var kw = instance.O.thumbnailFeaturedKeyword;
511 if( kw != '' ) {
512 // check if item featured based on a keyword in the title or in the description
513 kw = kw.toUpperCase();
514 var p = title.toUpperCase().indexOf(kw);
515 if( p > -1) {
516 item.featured = true;
517 // remove keyword case unsensitive
518 title = title.substring(0, p) + title.substring(p+kw.length, title.length);
519 }
520 p = description.toUpperCase().indexOf(kw);
521 if( p > -1) {
522 item.featured=true;
523 // remove keyword case unsensitive
524 description=description.substring(0, p) + description.substring(p + kw.length, description.length);
525 }
526 }
527
528 // TAGS
529 // if( instance.galleryFilterTags.Get() != false ) {
530 // if( instance.galleryFilterTags.Get() == true ) {
531 // if( tags != '' && tags != undefined ) {
532 // use set tags
533 // item.setTags(tags.split(' '));
534 // }
535 // }
536 // else {
537 // extract tags starting with # (in title)
538 if( typeof instance.galleryFilterTags.Get() == 'string' ) {
539 switch( instance.galleryFilterTags.Get().toUpperCase() ) {
540 case 'TITLE':
541 var re = /(?:^|\W)#(\w+)(?!\w)/g, match, matches = [];
542 var tags = "";
543 while (match = re.exec(title)) {
544 matches.push(match[1].replace(/^\s*|\s*$/, '')); //trim trailing/leading whitespace
545 }
546 item.setTags(matches); //tags;
547 title = title.split('#').join(''); //replaceall
548 break;
549 case 'DESCRIPTION':
550 var re = /(?:^|\W)#(\w+)(?!\w)/g, match, matches = [];
551 var tags = "";
552 while (match = re.exec(description)) {
553 matches.push(match[1].replace(/^\s*|\s*$/, '')); //trim trailing/leading whitespace
554 }
555 item.setTags(matches); //tags;
556 description = description.split('#').join(''); //replaceall
557 break;
558 }
559 }
560 else {
561 if( tags != '' && tags != undefined ) {
562 // use set tags
563 item.setTags(tags.split(' '));
564 }
565 }
566 // }
567 // }
568
569 // set (maybe modified) fields title and description
570 item.title = escapeHtml(instance, title);
571 item.description = escapeHtml(instance, description);
572 return item;
573 };
574
575
576 // removes logically current item
577 NGY2Item.prototype.delete = function( ) {
578 this.deleted = true;
579
580 // update content length of parent album
581 this.G.I[NGY2Item.GetIdx(this.G, this.albumID)].contentLength--;
582 this.G.I[NGY2Item.GetIdx(this.G, this.albumID)].numberItems--;
583
584 // check if in DOM and removes it
585 var nbTn = this.G.GOM.items.length;
586 var ID = this.GetID();
587 var foundIdx = -1;
588 var foundGOMidx = -1;
589 for( var i = 0; i < nbTn ; i++ ) {
590 var curTn = this.G.GOM.items[i];
591 var item=this.G.I[curTn.thumbnailIdx];
592 if( item.GetID() == ID ) {
593 // FOUND
594 if( !curTn.neverDisplayed ) {
595 foundIdx= curTn.thumbnailIdx;
596 foundGOMidx= i;
597 }
598 }
599 else {
600 if( foundIdx != -1 ) {
601 if( !curTn.neverDisplayed ) {
602 // update index value
603 item.$getElt('.nGY2GThumbnail').data('index', i-1);
604 item.$getElt('.nGY2GThumbnailImg').data('index', i-1);
605 }
606 }
607 }
608 }
609 if( foundIdx != -1 ) {
610 // delete item in GOM and delete thumbnail
611 var G = this.G;
612 if( this.selected == true ) {
613 this.selected = false;
614 G.GOM.nbSelected--; // update the global counter
615 }
616 if( G.I[foundIdx].$elt !== null ) {
617 G.I[foundIdx].$elt.remove(); // delete thumbnail DOM object
618 }
619 G.GOM.items.splice(foundGOMidx, 1); // delete in GOM
620 if( G.GOM.lastDisplayedIdx != -1 ) {
621 G.GOM.lastDisplayedIdx-=1;
622 }
623 }
624 }
625
626 NGY2Item.prototype.addToGOM = function( ) {
627 // retrieve index
628 var ID = this.GetID();
629 var l = this.G.I.length;
630 for( var idx = 0; idx < l; idx++ ) {
631 var item = this.G.I[idx];
632 if( item.GetID() == ID ) {
633 var w = item.thumbImg().width;
634 var h = item.thumbImg().height;
635 // set default size if required
636 if( h == 0 ) {
637 h = this.G.tn.defaultSize.getHeight();
638 }
639 if( w == 0 ) {
640 w = this.G.tn.defaultSize.getWidth();
641 }
642 // add to GOM -> will be displayed on next refresh/resize
643 var tn = new this.G.GOM.GTn(idx, w, h);
644 this.G.GOM.items.push(tn);
645 break;
646 }
647 }
648
649 }
650
651
652 // function to avoid XSS issue - Cross Site Scripting
653 // original: https://github.com/janl/mustache.js/blob/master/mustache.js#L55
654 var entityMap = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;', '/': '&#x2F;', '`': '&#x60;', '=': '&#x3D;' };
655 function escapeHtml (instance, string) {
656 if( instance.O.allowHTMLinData == true ) {
657 return string;
658 }
659 else {
660 return String(string).replace(/[&<>"'`=\/]/g, function fromEntityMap (s) {
661 return entityMap[s];
662 });
663 }
664 }
665
666
667 NGY2Item.get_nextId = function () {
668 return nextId;
669 };
670
671 //=== public (shared across instances)
672
673 //--- cached sub elements
674 NGY2Item.prototype.$getElt = function( elt, forceRefresh ) {
675 if( this.$elt == null ) { return null; }
676 if( this.$Elts[elt] !== undefined && !forceRefresh == true ) {
677 return this.$Elts[elt];
678 }
679 else {
680 if( elt == '.nGY2GThumbnail' ) {
681 this.$Elts[elt]=this.$elt;
682 }
683 else {
684 this.$Elts[elt]=this.$elt.find(elt);
685 }
686 return this.$Elts[elt];
687 }
688 };
689
690 // remove one element (in DOM and in cache)
691 NGY2Item.prototype.removeElt = function( elt ) {
692 if( this.$elt == null ) { return; }
693 if( this.$Elts[elt] == undefined) { return; }
694 this.$Elts[elt].remove();
695 var index = this.$Elts.indexOf(elt);
696 this.$Elts.splice(index, 1);
697 };
698
699 //--- returns the album containing the item
700 NGY2Item.prototype.album = function() {
701 return this.G.I[NGY2Item.GetIdx(this.G, this.albumID)];
702 };
703
704 //--- viewer - transition can be disabled per media kind - returns true if current media supports transition (swipe)
705 NGY2Item.prototype.mediaTransition = function( ) {
706 if( this.G.O.viewerTransitionMediaKind.indexOf( this.mediaKind ) > -1 ) {
707 return true;
708 }
709 return false;
710 };
711
712 //--- set one image (url and size)
713 NGY2Item.prototype.imageSet = function( src, w, h ) {
714 this.src = src;
715 this.width = w;
716 this.height = h;
717 };
718
719 //--- set one thumbnail (url and size) - screenSize and level are optionnal
720 NGY2Item.prototype.thumbSet = function( src, w, h, screenSize, level ) {
721 var lst=['xs','sm','me','la','xl'];
722 if( typeof screenSize === 'undefined' || screenSize == '' || screenSize == null ) {
723 for( var i=0; i< lst.length; i++ ) {
724 if( typeof level === 'undefined' || level == '' ) {
725 this.thumbs.url.l1[lst[i]]=src;
726 this.thumbs.height.l1[lst[i]]=h;
727 this.thumbs.width.l1[lst[i]]=w;
728 this.thumbs.url.lN[lst[i]]=src;
729 this.thumbs.height.lN[lst[i]]=h;
730 this.thumbs.width.lN[lst[i]]=w;
731 }
732 else {
733 this.thumbs.url[level][lst[i]]=src;
734 this.thumbs.height[level][lst[i]]=h;
735 this.thumbs.width[level][lst[i]]=w;
736 }
737 }
738 }
739 else {
740 if( typeof level === 'undefined' || level == '' || level == null ) {
741 this.thumbs.url.l1[screenSize]=src;
742 this.thumbs.height.l1[screenSize]=h;
743 this.thumbs.width.l1[screenSize]=w;
744 this.thumbs.url.lN[screenSize]=src;
745 this.thumbs.height.lN[screenSize]=h;
746 this.thumbs.width.lN[screenSize]=w;
747 }
748 else {
749 this.thumbs.url[level][screenSize]=src;
750 this.thumbs.height[level][screenSize]=h;
751 this.thumbs.width[level][screenSize]=w;
752 }
753 }
754
755 var lst=['xs','sm','me','la','xl'];
756 for( var i=0; i< lst.length; i++ ) {
757 this.thumbs.height.l1[lst[i]]=h;
758 }
759 for( var i=0; i< lst.length; i++ ) {
760 if( this.G.tn.settings.height.lN[lst[i]] == this.G.tn.settings.getH() && this.G.tn.settings.width.l1[lst[i]] == this.G.tn.settings.getW() ) {
761 this.thumbs.height.lN[lst[i]]=h;
762 }
763 }
764 };
765
766 //--- set thumbnail image real height for current level/resolution, and for all others level/resolutions having the same settings
767 NGY2Item.prototype.thumbSetImgHeight = function( h ) {
768 var lst=['xs','sm','me','la','xl'];
769 for( var i=0; i< lst.length; i++ ) {
770 if( this.G.tn.settings.height.l1[lst[i]] == this.G.tn.settings.getH() && this.G.tn.settings.width.l1[lst[i]] == this.G.tn.settings.getW() ) {
771 this.thumbs.height.l1[lst[i]]=h;
772 }
773 }
774 for( var i=0; i< lst.length; i++ ) {
775 if( this.G.tn.settings.height.lN[lst[i]] == this.G.tn.settings.getH() && this.G.tn.settings.width.l1[lst[i]] == this.G.tn.settings.getW() ) {
776 this.thumbs.height.lN[lst[i]]=h;
777 }
778 }
779 };
780
781 //--- set thumbnail image real width for current level/resolution, and for all others level/resolutions having the same settings
782 NGY2Item.prototype.thumbSetImgWidth = function( w ) {
783 var lst=['xs','sm','me','la','xl'];
784 for( var i=0; i< lst.length; i++ ) {
785 if( this.G.tn.settings.height.l1[lst[i]] == this.G.tn.settings.getH() && this.G.tn.settings.width.l1[lst[i]] == this.G.tn.settings.getW() ) {
786 this.thumbs.width.l1[lst[i]]=w;
787 }
788 }
789 for( var i=0; i< lst.length; i++ ) {
790 if( this.G.tn.settings.height.lN[lst[i]] == this.G.tn.settings.getH() && this.G.tn.settings.width.l1[lst[i]] == this.G.tn.settings.getW() ) {
791 this.thumbs.width.lN[lst[i]]=w;
792 }
793 }
794 };
795
796 //--- Returns Thumbnail image (depending of the screen resolution)
797 NGY2Item.prototype.thumbImg = function () {
798 var tnImg = { src: '', width: 0, height: 0 };
799
800 if( this.title == 'image gallery by nanogallery2 [build]' ) {
801 tnImg.src = this.G.emptyGif;
802 tnImg.url = this.G.emptyGif;
803 return tnImg;
804 }
805 tnImg.src = this.thumbs.url[this.G.GOM.curNavLevel][this.G.GOM.curWidth];
806 tnImg.width = this.thumbs.width[this.G.GOM.curNavLevel][this.G.GOM.curWidth];
807 tnImg.height = this.thumbs.height[this.G.GOM.curNavLevel][this.G.GOM.curWidth];
808 return tnImg;
809 };
810
811 //--- Set tags to items and add these tags to the album
812 NGY2Item.prototype.setTags = function( tags ) {
813 if( tags.length > 0 ) {
814 this.tags = tags;
815 var lstTags = this.album().albumTagList;
816 for( var i = 0; i < tags.length; i++ ) {
817 var tfound = false;
818 for( var j = 0; j < lstTags.length; j++ ) {
819 if( tags[i].toUpperCase() == lstTags[j].toUpperCase() ) {
820 tfound = true;
821 }
822 }
823 if( tfound == false) {
824 this.album().albumTagList.push(tags[i])
825 this.album().albumTagListSel.push(tags[i])
826 }
827 }
828 }
829 };
830
831 //--- check if 1 of current item's tags is selected (tag filter)
832 NGY2Item.prototype.checkTagFilter = function() {
833 if( this.G.galleryFilterTags.Get() != false && this.album().albumTagList.length > 0 ) {
834 if( this.G.O.thumbnailLevelUp && this.kind=='albumUp' ) {
835 return true;
836 }
837 var found = false;
838 var lstTags = this.album().albumTagListSel;
839 for( var i = 0; i < this.tags.length; i++ ) {
840 for( var j = 0; j < lstTags.length; j++ ) {
841 if( this.tags[i].toUpperCase() == lstTags[j].toUpperCase() ) {
842 found = true;
843 break;
844 }
845 }
846 }
847 return found;
848 }
849 else
850 return true;
851 };
852
853 //--- check if 1 of current item's tags is found using API search
854 NGY2Item.prototype.isSearchTagFound = function() {
855 if( this.G.GOM.albumSearchTags == '' ) { return true; }
856 if( this.G.O.thumbnailLevelUp && this.kind == 'albumUp' ) { return true; }
857
858 //var lstTags=this.album().albumTagListSel;
859 for( var i = 0; i < this.tags.length; i++ ) {
860 if( this.tags[i].toUpperCase().indexOf( this.G.GOM.albumSearchTags ) >= 0 ) {
861 return true;
862 }
863 }
864 return false;
865 };
866
867 //--- set the URL of the media to display in the viewer
868 //--- markup is defined for images
869 NGY2Item.prototype.setMediaURL = function( url, mediaKind ) {
870 this.src = url;
871 this.mediaKind = mediaKind;
872 if( mediaKind == 'img' ) {
873 this.mediaMarkup = '<img class="nGY2ViewerMedia" src="' + url + '" alt=" " itemprop="contentURL" draggable="false">';
874 }
875 };
876
877
878 //--- check if current item should be displayed
879 NGY2Item.prototype.isToDisplay = function( albumID ) {
880 return this.albumID == albumID && this.checkTagFilter() && this.isSearchFound() && this.isSearchTagFound() && this.deleted == false;
881 };
882
883
884
885 //--- returns the number of items of the current album
886 //--- count using tags filter
887 NGY2Item.prototype.getContentLength = function( filterTags ) {
888 if( filterTags == false || this.albumTagList.length == 0 || this.G.galleryFilterTags.Get() == false ) {
889 return this.contentLength;
890 }
891 else {
892 var l = this.G.I.length;
893 var cnt = 0;
894 var albumID = this.GetID();
895 for( var idx = 0; idx < l; idx++ ) {
896 var item = this.G.I[idx];
897 if( item.isToDisplay(albumID) ) {
898 cnt++;
899 }
900 }
901 return cnt;
902 }
903 };
904
905 NGY2Item.prototype.isSearchFound = function() {
906 if( this.G.GOM.albumSearch != '' ) {
907 if( this.title.toUpperCase().indexOf( this.G.GOM.albumSearch ) == -1 ) {
908 return false;
909 }
910 }
911 return true;
912 }
913
914
915 //--- for future use...
916 NGY2Item.prototype.responsiveURL = function () {
917 var url = '';
918 switch(this.G.O.kind) {
919 case '':
920 url = this.src;
921 break;
922 case 'flickr':
923 url = this.src;
924 break;
925 case 'picasa':
926 case 'google':
927 case 'google2':
928 default:
929 url = this.src;
930 break;
931 }
932 return url;
933 };
934
935
936 //--- Reveal the thumbnail image with animation on opacity
937 NGY2Item.prototype.ThumbnailImageReveal = function () {
938
939 if( this.thumbnailImgRevealed == false ) {
940 this.thumbnailImgRevealed = true;
941 new NGTweenable().tween({
942 from: { opacity: 0 },
943 to: { opacity: 1 },
944 attachment: { item: this },
945 delay: 30,
946 duration: 400,
947 easing: 'easeOutQuart',
948 step: function (state, att) {
949 var $e=att.item.$getElt('.nGY2TnImg');
950 if( $e != null ) {
951 $e.css( state );
952 }
953 }
954 });
955 }
956 };
957
958
959 // In case of thumbnails with stacks - apply a percent to a value which include a unit
960 function ValueApplyPercent( str, percent ) {
961 str=String(str);
962 if( str === '0' || percent == 1 ) { return str; }
963 var n = Number(str.replace(/[a-zA-Z]/g, ''));
964 var ar = str.match(/([^\-0-9\.]+)/g);
965 var a = '';
966 if( ar != null && ar.length > 0 ) {
967 a = ar.join();
968 }
969
970 if( isNaN(n) || n == 0 ) {
971 return str;
972 }
973
974 n = n * percent;
975 return n + a;
976 }
977
978 //--- 2D/3D CSS transform - apply the cached value to element
979 NGY2Item.prototype.CSSTransformApply = function ( eltClass ) {
980 var obj = this.eltTransform[eltClass];
981
982 if( eltClass == '.nGY2GThumbnail' ) {
983 // thumbnail
984 var nbStacks = obj.$elt.length-1;
985 var pTranslateX = 1;
986 var pTranslateY = 1;
987 var pTranslateZ = 1;
988 var pTranslate = 1;
989 var pRotateX = 1;
990 var pRotateY = 1;
991 var pRotateZ = 1;
992 var pRotate = 1;
993 var pScale = 1;
994 for( var n = nbStacks; n >= 0; n-- ) {
995 // units must be given with
996 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) + ')';
997 if( !(this.G.IE <= 9) && !this.G.isGingerbread ) {
998 v += ' rotateX(' + ValueApplyPercent(obj.rotateX,pRotateX) + ') rotateY(' + ValueApplyPercent(obj.rotateY,pRotateY) + ') rotateZ(' + ValueApplyPercent(obj.rotateZ,pRotateZ) + ') rotate(' + ValueApplyPercent(obj.rotate,pRotate) + ')';
999 }
1000 else {
1001 v += ' rotate(' + ValueApplyPercent(obj.rotateZ,pRotateZ) + ')';
1002 }
1003 obj.$elt[n].style[this.G.CSStransformName] = v;
1004
1005 if( nbStacks > 0 ) {
1006 // apply a percent to the stack elements
1007 pTranslateX -= this.G.tn.opt.Get('stacksTranslateX');
1008 pTranslateY -= this.G.tn.opt.Get('stacksTranslateY');
1009 pTranslateZ -= this.G.tn.opt.Get('stacksTranslateZ');
1010 pRotateX -= this.G.tn.opt.Get('stacksRotateX');
1011 pRotateY -= this.G.tn.opt.Get('stacksRotateY');
1012 pRotateZ -= this.G.tn.opt.Get('stacksRotateZ');
1013 pScale -= this.G.tn.opt.Get('stacksScale');
1014 }
1015 }
1016 }
1017 else {
1018 // thumbnail sub element
1019 if( obj.$elt != null ) {
1020 for( var n = 0; n < obj.$elt.length; n++ ) {
1021 if( obj.$elt[n] != undefined ) {
1022 // units must be given with
1023 var v = 'translateX(' + obj.translateX + ') translateY(' + obj.translateY + ') translateZ(' + obj.translateZ + ') scale(' + obj.scale + ') translate(' + obj.translate + ')';
1024 if( !(this.G.IE <= 9) && !this.G.isGingerbread ) {
1025 v += ' rotateX(' + obj.rotateX + ') rotateY(' + obj.rotateY + ') rotateZ(' + obj.rotateZ + ') rotate(' + obj.rotate + ')';
1026 }
1027 else {
1028 v += ' rotate(' + obj.rotateZ + ')';
1029 }
1030 obj.$elt[n].style[this.G.CSStransformName] = v;
1031 }
1032 }
1033 }
1034 }
1035 };
1036
1037 //--- 2D/3D CSS transform - set a value in cache
1038 NGY2Item.prototype.CSSTransformSet = function ( eltClass, transform, value, forceRefresh ) {
1039 if( this.eltTransform[eltClass] == undefined ) {
1040 this.eltTransform[eltClass] = { translateX: 0, translateY: 0, translateZ: 0, rotateX: 0, rotateY: 0, rotateZ: 0, scale: 1, translate: '0px,0px', rotate: 0 };
1041 this.eltTransform[eltClass].$elt = this.$getElt(eltClass);
1042 }
1043 this.eltTransform[eltClass][transform] = value;
1044 if( forceRefresh === true ) {
1045 this.eltTransform[eltClass].$elt = this.$getElt(eltClass, true);
1046 }
1047 };
1048
1049 //--- CSS Filters - apply the cached value to element
1050 NGY2Item.prototype.CSSFilterApply = function ( eltClass ) {
1051 var obj = this.eltFilter[eltClass];
1052 var v = 'blur(' + obj.blur + ') brightness(' + obj.brightness + ') grayscale(' + obj.grayscale + ') sepia(' + obj.sepia + ') contrast(' + obj.contrast + ') opacity(' + obj.opacity + ') saturate(' + obj.saturate + ')';
1053 if( obj.$elt != null ) {
1054 for( var n = 0; n < obj.$elt.length; n++ ) {
1055 if( obj.$elt[n] != undefined ) {
1056 obj.$elt[n].style.WebkitFilter = v;
1057 obj.$elt[n].style.filter = v;
1058 }
1059 }
1060 }
1061 };
1062
1063 //--- CSS Filters - set a value in cache
1064 NGY2Item.prototype.CSSFilterSet = function ( eltClass, filter, value, forceRefresh ) {
1065 if( this.eltFilter[eltClass] == undefined ) {
1066 this.eltFilter[eltClass] = { blur: 0, brightness: '100%', grayscale: '0%', sepia: '0%', contrast: '100%', opacity: '100%', saturate: '100%' };
1067 this.eltFilter[eltClass].$elt = this.$getElt(eltClass);
1068 }
1069 this.eltFilter[eltClass][filter] = value;
1070 if( forceRefresh === true ) {
1071 this.eltTransform[eltClass].$elt = this.$getElt(eltClass, true);
1072 }
1073 };
1074
1075 //--- thumbnail hover animation
1076 NGY2Item.prototype.animate = function ( effect, delay, hoverIn ) {
1077 if( this.$getElt() == null ) { return; }
1078
1079 var context = {};
1080 context.G = this.G;
1081 context.item = this;
1082 context.effect = effect;
1083 context.hoverIn = hoverIn;
1084 context.cssKind = '';
1085 if( hoverIn ) {
1086 // HOVER IN
1087
1088 if( this.eltEffect[effect.element] == undefined ) {
1089 this.eltEffect[effect.element] = [];
1090 }
1091 if( this.eltEffect[effect.element][effect.type] == undefined ) {
1092 this.eltEffect[effect.element][effect.type] = { initialValue: 0, lastValue: 0 };
1093 }
1094 if( effect.firstKeyframe ) {
1095 // store initial and current value -> for use in the back animation
1096 this.eltEffect[effect.element][effect.type] = { initialValue: effect.from, lastValue: effect.from};
1097 }
1098
1099 context.animeFrom = effect.from;
1100 context.animeTo = effect.to;
1101 context.animeDuration = parseInt(effect.duration);
1102 context.animeDelay = 30 + parseInt(effect.delay + delay); // 30ms is a default delay to avoid conflict with other initializations
1103 context.animeEasing = effect.easing;
1104 }
1105 else {
1106 // HOVER OUT
1107 if( effect.firstKeyframe ) {
1108 context.animeFrom = this.eltEffect[effect.element][effect.type].lastValue;
1109 context.animeTo = this.eltEffect[effect.element][effect.type].initialValue;
1110 // context.animeTo=effect.from;
1111 }
1112 else {
1113 // context.animeFrom=effect.from;
1114 context.animeFrom = this.eltEffect[effect.element][effect.type].lastValue;
1115 context.animeTo = this.eltEffect[effect.element][effect.type].initialValue;
1116 // context.animeTo=effect.to;
1117
1118 }
1119
1120 context.animeDuration = parseInt(effect.durationBack);
1121 context.animeDelay = 30 + parseInt(effect.delayBack + delay); // 30ms is a default delay to avoid conflict with other initializations
1122 context.animeEasing = effect.easingBack;
1123 }
1124
1125
1126 // detect if animation on CSS transform
1127 var transform=['translateX', 'translateY', 'translateZ', 'scale', 'rotateX', 'rotateY', 'rotateZ'];
1128 for( var i = 0; i < transform.length; i++ ) {
1129 if( effect.type == transform[i] ) {
1130 context.cssKind = 'transform';
1131 break;
1132 }
1133 }
1134
1135 // detect if animation on CSS filter
1136 var filter=['blur', 'brightness', 'grayscale', 'sepia', 'contrast', 'opacity', 'saturate'];
1137 for( var i = 0; i < filter.length; i++ ) {
1138 if( effect.type == filter[i] ) {
1139 context.cssKind = 'filter';
1140 break;
1141 }
1142 }
1143 // handle some special cases
1144 if( hoverIn && effect.element == '.nGY2GThumbnail' && ( effect.type == 'scale' || effect.type == 'rotateX') ) {
1145 this.G.GOM.lastZIndex++;
1146 this.$getElt(effect.element).css('z-index', this.G.GOM.lastZIndex);
1147 // setElementOnTop(this.G.$E.base, this.$getElt(effect.element) );
1148 }
1149
1150 // animation
1151 var tweenable = new NGTweenable();
1152 context.tweenable=tweenable;
1153 tweenable.tween({
1154 attachment: context,
1155 from: { 'v': context.animeFrom },
1156 to: { 'v': context.animeTo },
1157 duration: context.animeDuration, //parseInt(effect.duration),
1158 delay: context.animeDelay, //parseInt(effect.delay),
1159 easing: context.animeEasing, //'easeOutQuart',
1160
1161 step: function (state, att) {
1162 if( att.item.$getElt() == null ) {
1163 // the thumbnail may be destroyed since the start of the animation
1164 att.tweenable.stop(false);
1165 // att.tweenable.dispose();
1166 return;
1167 }
1168 if( att.hoverIn && !att.item.hovered ) {
1169 // thumbnail no more hovered
1170 att.tweenable.stop(false);
1171 // att.tweenable.dispose();
1172 return;
1173 }
1174
1175 if( att.G.VOM.viewerDisplayed ) {
1176 att.tweenable.stop(false);
1177 // att.tweenable.dispose();
1178 return;
1179 }
1180
1181 // test if in delay phase
1182 if( state.v == att.animeFrom ) { return; }
1183
1184 switch( att.cssKind ) {
1185 case 'transform':
1186 // window.ng_draf( function() {
1187 att.item.CSSTransformSet(att.effect.element, att.effect.type, state.v);
1188 att.item.CSSTransformApply( att.effect.element );
1189 // });
1190 break;
1191 case 'filter':
1192 // window.ng_draf( function() {
1193 att.item.CSSFilterSet(att.effect.element, att.effect.type, state.v);
1194 att.item.CSSFilterApply( att.effect.element );
1195 // });
1196 break;
1197 default:
1198 var v=state.v;
1199 if( state.v.substring(0,4) == 'rgb(' || state.v.substring(0,5) == 'rgba(' ) {
1200 // to remove values after the dot (not supported by RGB/RGBA)
1201 // v=ngtinycolor(state.v).toRgbString();
1202 v = ShadeBlendConvert(0, v);
1203 }
1204 // window.ng_draf( function() {
1205 att.item.$getElt( att.effect.element ).css( att.effect.type, v );
1206 // });
1207 break;
1208 }
1209 if( hoverIn ) {
1210 // store value for back animation
1211 att.item.eltEffect[att.effect.element][att.effect.type].lastValue = state.v;
1212 }
1213 },
1214
1215 finish: function (state, att) {
1216 if( hoverIn ) {
1217 // store value for back animation
1218 att.item.eltEffect[att.effect.element][att.effect.type].lastValue = state.v;
1219 }
1220
1221 if( att.item.$getElt() == null ) {
1222 // the thumbnail may be destroyed since the start of the animation
1223 return;
1224 }
1225 if( att.hoverIn && !att.item.hovered ) {
1226 // thumbnail no more hovered
1227 return;
1228 }
1229
1230 if( att.G.VOM.viewerDisplayed ) {
1231 return;
1232 }
1233
1234 // window.ng_draf( function() {
1235 switch( att.cssKind ) {
1236 case 'transform':
1237 att.item.CSSTransformSet(att.effect.element, att.effect.type, att.animeTo);
1238 att.item.CSSTransformApply(att.effect.element);
1239 break;
1240 case 'filter':
1241 att.item.CSSFilterSet(att.effect.element, att.effect.type, att.animeTo);
1242 att.item.CSSFilterApply(att.effect.element);
1243 break;
1244 default:
1245 att.item.$getElt(att.effect.element).css(att.effect.type, att.animeTo);
1246 break;
1247 }
1248 // });
1249 }
1250 });
1251 };
1252
1253 return NGY2Item;
1254 })();
1255
1256 }
1257
1258 _this.options = jQuery.extend(true, {}, jQuery.nanogallery2.defaultOptions, options);
1259 // Initialization code
1260 _this.nG2 = null;
1261 _this.nG2 = new nanoGALLERY2();
1262 _this.nG2.initiateGallery2(_this.e, _this.options );
1263
1264 };
1265
1266 // PUBLIC EXPOSED METHODS
1267 _this.test = function() {
1268 //alert('test');
1269 // console.dir(_this.nG.G.I.length);
1270 // console.dir(_this.nG);
1271 //privateTest();
1272 }
1273
1274
1275 // Run initializer
1276 _this.init();
1277 };
1278
1279 jQuery.nanogallery2.defaultOptions = {
1280 kind : '',
1281 userID : '',
1282 photoset : '',
1283 album: '',
1284 blackList : 'scrapbook|profil|auto backup',
1285 whiteList : '',
1286 albumList : '',
1287 albumList2 : null,
1288 RTL : false,
1289 poogleplusUseUrlCrossDomain : true,
1290 flickrSkipOriginal : true,
1291 breadcrumbAutoHideTopLevel : true,
1292 displayBreadcrumb : true,
1293 breadcrumbOnlyCurrentLevel : true,
1294 breadcrumbHideIcons : true,
1295 theme : 'nGY2',
1296 galleryTheme : 'dark',
1297 viewerTheme : 'dark',
1298 items : null,
1299 itemsBaseURL : '',
1300 thumbnailSelectable : false,
1301 dataProvider: '',
1302 dataCharset: 'Latin',
1303 allowHTMLinData: false,
1304 locationHash : true,
1305 slideshowDelay : 3000,
1306 slideshowAutoStart : false,
1307
1308 debugMode: false,
1309
1310 galleryDisplayMoreStep : 2,
1311 galleryDisplayMode : 'fullContent',
1312 galleryL1DisplayMode : null,
1313 galleryPaginationMode : 'rectangles', // 'dots', 'rectangles', 'numbers'
1314 // galleryThumbnailsDisplayDelay : 2000,
1315 galleryMaxRows : 2,
1316 galleryL1MaxRows : null,
1317 galleryLastRowFull: false,
1318 galleryLayoutEngine : 'default',
1319 paginationSwipe: true,
1320 paginationVisiblePages : 10,
1321 // paginationSwipeSensibilityVert : 10,
1322 galleryFilterTags : false, // possible values: false, true, 'title', 'description'
1323 galleryL1FilterTags : null, // possible values: false, true, 'title', 'description'
1324 galleryMaxItems : 0, // maximum number of items per album --> only flickr, google2, nano_photos_provider2
1325 galleryL1MaxItems : null, // maximum number of items per gallery page --> only flickr, google2, nano_photos_provider2
1326 gallerySorting : '',
1327 galleryL1Sorting : null,
1328 galleryDisplayTransition : 'none',
1329 galleryL1DisplayTransition : null,
1330 galleryDisplayTransitionDuration : 1000,
1331 galleryL1DisplayTransitionDuration : null,
1332 galleryResizeAnimation : true,
1333 galleryRenderDelay : 60,
1334
1335 thumbnailCrop : true,
1336 thumbnailL1Crop : null,
1337 thumbnailCropScaleFactor : 1.5,
1338 thumbnailLevelUp : false,
1339 thumbnailAlignment : 'fillWidth',
1340 thumbnailWidth : 300,
1341 thumbnailL1Width : null,
1342 thumbnailHeight : 200,
1343 thumbnailL1Height : null,
1344 thumbnailBaseGridHeight : 0,
1345 thumbnailL1BaseGridHeight : null,
1346 thumbnailGutterWidth : 2,
1347 thumbnailL1GutterWidth : null,
1348 thumbnailGutterHeight : 2,
1349 thumbnailL1GutterHeight : null,
1350 thumbnailBorderVertical : 2,
1351 thumbnailBorderHorizontal : 2,
1352 thumbnailFeaturedKeyword : '*featured',
1353 thumbnailAlbumDisplayImage : false,
1354 thumbnailHoverEffect2 : 'toolsAppear',
1355 thumbnailBuildInit2 : '',
1356 thumbnailStacks : 0,
1357 thumbnailL1Stacks : null,
1358 thumbnailStacksTranslateX : 0,
1359 thumbnailL1StacksTranslateX : null,
1360 thumbnailStacksTranslateY : 0,
1361 thumbnailL1StacksTranslateY : null,
1362 thumbnailStacksTranslateZ : 0,
1363 thumbnailL1StacksTranslateZ : null,
1364 thumbnailStacksRotateX : 0,
1365 thumbnailL1StacksRotateX : null,
1366 thumbnailStacksRotateY : 0,
1367 thumbnailL1StacksRotateY : null,
1368 thumbnailStacksRotateZ : 0,
1369 thumbnailL1StacksRotateZ : null,
1370 thumbnailStacksScale : 0,
1371 thumbnailL1StacksScale : null,
1372 thumbnailDisplayOutsideScreen: true,
1373 thumbnailWaitImageLoaded: true,
1374 thumbnailSliderDelay: 2000,
1375 galleryBuildInit2 : '',
1376 portable : false,
1377 eventsDebounceDelay: 30,
1378
1379 touchAnimation : true,
1380 touchAnimationL1 : undefined,
1381 touchAutoOpenDelay : 0,
1382
1383 thumbnailLabel : {
1384 position : 'overImageOnBottom',
1385 align: 'center',
1386 display : true,
1387 displayDescription : false,
1388 titleMaxLength : 0,
1389 titleMultiLine : false,
1390 descriptionMaxLength : 0,
1391 descriptionMultiLine : false,
1392 hideIcons : true,
1393 title : ''
1394 },
1395
1396 thumbnailToolbarImage : { topLeft: 'select', topRight : 'featured' },
1397 thumbnailToolbarAlbum : { topLeft: 'select', topRight : 'counter' },
1398 thumbnailDisplayInterval : 15,
1399 thumbnailL1DisplayInterval : null,
1400 thumbnailDisplayTransition : 'fadeIn',
1401 thumbnailL1DisplayTransition : null,
1402 thumbnailDisplayTransitionDuration: 240,
1403 thumbnailL1DisplayTransitionDuration: null,
1404 thumbnailOpenImage : true,
1405 thumbnailOpenOriginal : false,
1406 thumbnailGlobalImageTitle : '',
1407 thumbnailGlobalAlbumTitle : '',
1408
1409 viewer : 'internal',
1410 viewerFullscreen: false,
1411 viewerDisplayLogo : false,
1412 imageTransition : 'swipe2',
1413 viewerTransitionMediaKind : 'img',
1414 viewerZoom : true,
1415 viewerImageDisplay : '',
1416 openOnStart : '',
1417 viewerHideToolsDelay : 3000,
1418 viewerToolbar : {
1419 display : true,
1420 position : 'bottomOverImage',
1421 fullWidth : true,
1422 align : 'center',
1423 autoMinimize : 0,
1424 standard : 'minimizeButton,label',
1425 minimized : 'minimizeButton,label,infoButton,shareButton,downloadButton,linkOriginalButton,fullscreenButton'
1426 },
1427 viewerTools : {
1428 topLeft : 'pageCounter,playPauseButton',
1429 topRight : 'rotateLeft,rotateRight,fullscreenButton,closeButton'
1430 },
1431
1432 breakpointSizeSM : 480,
1433 breakpointSizeME : 992,
1434 breakpointSizeLA : 1200,
1435 breakpointSizeXL : 1800,
1436
1437 fnThumbnailInit : null,
1438 fnThumbnailHoverInit : null,
1439 fnThumbnailHover : null,
1440 fnThumbnailHoverOut : null,
1441 fnThumbnailDisplayEffect : null,
1442 fnViewerInfo : null,
1443 fnImgToolbarCustInit : null,
1444 fnImgToolbarCustDisplay : null,
1445 fnImgToolbarCustClick : null,
1446 fnProcessData : null,
1447 fnThumbnailSelection : null,
1448 fnGalleryRenderStart : null,
1449 fnGalleryRenderEnd : null,
1450 fnGalleryObjectModelBuilt : null,
1451 fnGalleryLayoutApplied : null,
1452 fnThumbnailClicked : null,
1453 fnShoppingCartUpdated : null,
1454 fnThumbnailToolCustAction : null,
1455 fnThumbnailOpen : null,
1456 fnImgDisplayed : null,
1457
1458 i18n : {
1459 'breadcrumbHome' : 'Galleries', 'breadcrumbHome_FR' : 'Galeries',
1460 'thumbnailImageTitle' : '', 'thumbnailAlbumTitle' : '',
1461 'thumbnailImageDescription' : '', 'thumbnailAlbumDescription' : '',
1462 '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'
1463 },
1464 icons : {
1465 // sample for font awesome: <i style="color:#eee;" class="fa fa-search-plus"></i>
1466 thumbnailAlbum: '<i class="nGY2Icon-folder-empty"></i>',
1467 thumbnailImage: '<i class="nGY2Icon-picture"></i>',
1468 breadcrumbAlbum: '<i class="nGY2Icon-folder-empty"></i>',
1469 breadcrumbHome: '<i class="nGY2Icon-home"></i>',
1470 breadcrumbSeparator: '<i class="nGY2Icon-left-open"></i>',
1471 breadcrumbSeparatorRtl: '<i class="nGY2Icon-right-open"></i>',
1472 navigationFilterSelected: '<i style="color:#fff;" class="nGY2Icon-toggle-on"></i>',
1473 navigationFilterUnselected: '<i style="color:#ddd;" class="nGY2Icon-toggle-off"></i>',
1474 navigationFilterSelectedAll: '<i class="nGY2Icon-toggle-on"></i><i class="nGY2Icon-ok"></i>',
1475 thumbnailSelected: '<i style="color:#bff;" class="nGY2Icon-ok-circled"></i>',
1476 thumbnailUnselected: '<i style="color:#bff;" class="nGY2Icon-circle-empty"></i>',
1477 thumbnailFeatured: '<i style="color:#dd5;" class="nGY2Icon-star"></i>',
1478 thumbnailCounter: '<i class="nGY2Icon-picture"></i>',
1479 thumbnailShare: '<i class="nGY2Icon-ngy2_share2"></i>',
1480 thumbnailDownload: '<i class="nGY2Icon-ngy2_download2"></i>',
1481 thumbnailInfo: '<i class="nGY2Icon-ngy2_info2"></i>',
1482 thumbnailCart: '<i class="nGY2Icon-basket"></i>',
1483 thumbnailDisplay: '<i class="nGY2Icon-ngy2_zoom_in2"></i>',
1484 thumbnailCustomTool1: 'T1',
1485 thumbnailCustomTool2: 'T2',
1486 thumbnailCustomTool3: 'T3',
1487 thumbnailCustomTool4: 'T4',
1488 thumbnailCustomTool5: 'T5',
1489 thumbnailCustomTool6: 'T6',
1490 thumbnailCustomTool7: 'T7',
1491 thumbnailCustomTool8: 'T8',
1492 thumbnailCustomTool9: 'T9',
1493 thumbnailCustomTool10: 'T10',
1494 thumbnailAlbumUp: '<i style="font-size: 3em;" class="nGY2Icon-ngy2_chevron_up2"></i>',
1495 paginationNext: '<i class="nGY2Icon-right-open"></i>',
1496 paginationPrevious: '<i class="nGY2Icon-left-open"></i>',
1497 galleryMoreButton: '<i class="nGY2Icon-picture"></i> &nbsp; <i class="nGY2Icon-right-open"></i>',
1498 buttonClose: '<i class="nGY2Icon-ngy2_close2"></i>',
1499 viewerPrevious: '<i class="nGY2Icon-ngy2_chevron-left"></i>',
1500 viewerNext: '<i class="nGY2Icon-ngy2_chevron-right"></i>',
1501 viewerImgPrevious: '<i class="nGY2Icon-ngy2_chevron_left3"></i>',
1502 viewerImgNext: '<i class="nGY2Icon-ngy2_chevron_right3"></i>',
1503 viewerDownload: '<i class="nGY2Icon-ngy2_download2"></i>',
1504 viewerToolbarMin: '<i class="nGY2Icon-ellipsis-vert"></i>',
1505 viewerToolbarStd: '<i class="nGY2Icon-menu"></i>',
1506 viewerPlay: '<i class="nGY2Icon-play"></i>',
1507 viewerPause: '<i class="nGY2Icon-pause"></i>',
1508 viewerFullscreenOn: '<i class="nGY2Icon-resize-full"></i>',
1509 viewerFullscreenOff: '<i class="nGY2Icon-resize-small"></i>',
1510 viewerZoomIn: '<i class="nGY2Icon-ngy2_zoom_in2"></i>',
1511 viewerZoomOut: '<i class="nGY2Icon-ngy2_zoom_out2"></i>',
1512 viewerLinkOriginal: '<i class="nGY2Icon-ngy2_external2"></i>',
1513 viewerInfo: '<i class="nGY2Icon-ngy2_info2"></i>',
1514 viewerShare: '<i class="nGY2Icon-ngy2_share2"></i>',
1515 viewerRotateLeft: '<i class="nGY2Icon-ccw"></i>',
1516 viewerRotateRight: '<i class="nGY2Icon-cw"></i>',
1517 user: '<i class="nGY2Icon-user"></i>',
1518 location: '<i class="nGY2Icon-location"></i>',
1519 config: '<i class="nGY2Icon-wrench"></i>',
1520 shareFacebook: '<i style="color:#3b5998;" class="nGY2Icon-facebook-squared"></i>',
1521 shareTwitter: '<i style="color:#00aced;" class="nGY2Icon-twitter-squared"></i>',
1522 // shareGooglePlus: '<i style="color:#dd4b39;" class="nGY2Icon-gplus-squared"></i>',
1523 shareTumblr: '<i style="color:#32506d;" class="nGY2Icon-tumblr-squared"></i>',
1524 sharePinterest: '<i style="color:#cb2027;" class="nGY2Icon-pinterest-squared"></i>',
1525 shareVK: '<i style="color:#3b5998;" class="nGY2Icon-vkontakte"></i>',
1526 shareMail: '<i style="color:#555;" class="nGY2Icon-mail-alt"></i>',
1527 viewerCustomTool1: 'T1',
1528 viewerCustomTool2: 'T2',
1529 viewerCustomTool3: 'T3',
1530 viewerCustomTool4: 'T4',
1531 viewerCustomTool5: 'T5',
1532 viewerCustomTool6: 'T6',
1533 viewerCustomTool7: 'T7',
1534 viewerCustomTool8: 'T8',
1535 viewerCustomTool9: 'T9',
1536 viewerCustomTool10: 'T10'
1537 }
1538 };
1539
1540 jQuery.fn.nanogallery2 = function (args, option, value) {
1541
1542 if( typeof jQuery(this).data('nanogallery2data') === 'undefined'){
1543 if( args == 'destroy' ) {
1544 // command to destroy but no instance yet --> exit
1545 return;
1546 }
1547
1548 return this.each( function(){
1549 (new jQuery.nanogallery2(this, args));
1550 });
1551 }
1552 else {
1553 // no options -->
1554 // This function breaks the chain, but provides some API methods
1555
1556 var nG2=$(this).data('nanogallery2data').nG2;
1557 switch(args){
1558 case 'displayItem':
1559 nG2.DisplayItem(option);
1560 break;
1561
1562 case 'search':
1563 return( nG2.Search(option));
1564 break;
1565
1566 case 'search2':
1567 return nG2.Search2(option, value);
1568 break;
1569
1570 case 'search2Execute':
1571 return nG2.Search2Execute();
1572 break;
1573
1574 case 'refresh':
1575 nG2.Refresh();
1576 break;
1577
1578 case 'resize':
1579 nG2.Resize();
1580 break;
1581
1582 case 'instance':
1583 return nG2;
1584 break;
1585
1586 case 'data':
1587 nG2.data= {
1588 items: nG2.I,
1589 gallery: nG2.GOM,
1590 lightbox: nG2.VOM
1591 };
1592 return nG2.data;
1593 break;
1594
1595 case 'reload':
1596 nG2.ReloadAlbum();
1597 return $(this);
1598 break;
1599
1600 case 'itemsSelectedGet':
1601 return nG2.ItemsSelectedGet();
1602 break;
1603
1604 case 'itemsSetSelectedValue':
1605 nG2.ItemsSetSelectedValue(option, value);
1606 break;
1607
1608 case 'option':
1609 if(typeof value === 'undefined'){
1610 return nG2.Get(option);
1611 }else{
1612 nG2.Set(option,value);
1613 if( option == 'demoViewportWidth' ) {
1614 // force resize event -> for demo purposes
1615 $(window).trigger('resize');
1616 }
1617 }
1618 break;
1619
1620 case 'destroy':
1621 nG2.Destroy();
1622 $(this).removeData('nanogallery2data');
1623 break;
1624
1625 case 'shoppingCartGet':
1626 return nG2.shoppingCart;
1627 break;
1628
1629 case 'shoppingCartUpdate':
1630 if( typeof value === 'undefined' || typeof option === 'undefined' ){
1631 return false;
1632 }
1633 var ID = option;
1634 var cnt = value;
1635 for( var i = 0; i < nG2.shoppingCart.length; i++) {
1636 if( nG2.shoppingCart[i].ID = ID ) {
1637 nG2.shoppingCart[i].cnt = cnt;
1638 }
1639 }
1640 var fu = G.O.fnShoppingCartUpdated;
1641 if( fu !== null ) {
1642 typeof fu == 'function' ? fu(nG2.shoppingCart, NGY2Item.Get(G, ID)) : window[fu](nG2.shoppingCart, NGY2Item.Get(G, ID));
1643 }
1644 return nG2.shoppingCart;
1645 break;
1646
1647 case 'shoppingCartRemove':
1648 if( typeof option === 'undefined' ){
1649 return false;
1650 }
1651 var ID=option;
1652 for( var i=0; i<nG2.shoppingCart.length; i++) {
1653 if( nG2.shoppingCart[i].ID=ID ) {
1654 nG2.shoppingCart.splice(i,1);
1655 break;
1656 }
1657 }
1658 var fu = G.O.fnShoppingCartUpdated;
1659 if( fu !== null ) {
1660 typeof fu == 'function' ? fu(nG2.shoppingCart, NGY2Item.Get(G, ID)) : window[fu](nG2.shoppingCart, NGY2Item.Get(G, ID));
1661 }
1662 return nG2.shoppingCart;
1663 break;
1664
1665 case 'closeViewer':
1666 nG2.CloseViewer();
1667 break;
1668 case 'minimizeToolbar':
1669 nG2.MinimizeToolbar();
1670 break;
1671 case 'maximizeToolbar':
1672 nG2.MaximizeToolbar();
1673 break;
1674 case 'paginationPreviousPage':
1675 nG2.PaginationPreviousPage();
1676 break;
1677 case 'paginationNextPage':
1678 nG2.paginationNextPage();
1679 break;
1680 case 'paginationGotoPage':
1681 nG2.PaginationGotoPage( option );
1682 break;
1683 case 'paginationCountPages':
1684 nG2.PaginationCountPages();
1685 break;
1686
1687 }
1688 return $(this);
1689
1690 }
1691 };
1692
1693
1694 // ###############################
1695 // ##### nanogallery2 script #####
1696 // ###############################
1697
1698 /** @function nanoGALLERY2 */
1699 function nanoGALLERY2() {
1700 "use strict";
1701
1702 /**
1703 * Force reload the current album, if provided by Json
1704 */
1705 this.ReloadAlbum = function(){
1706 if( G.O.kind === '' ) {
1707 throw 'Not supported for this content source:' + G.O.kind;
1708 }
1709
1710 var albumIdx = G.GOM.albumIdx;
1711 if( albumIdx == -1 ) {
1712 throw ('Current album not found.');
1713 }
1714
1715 var albumID = G.I[albumIdx].GetID();
1716
1717 // unselect everything & remove link to album (=logical delete)
1718 var l = G.I.length;
1719 for( var i = 0; i < l ; i++ ) {
1720 var item = G.I[i];
1721 if( item.albumID == albumID ) {
1722 item.selected = false;
1723 }
1724 }
1725
1726 G.I[albumIdx].contentIsLoaded = false;
1727
1728 DisplayAlbum('-1', albumID);
1729 };
1730
1731 /**
1732 * Set one or several items selected/unselected
1733 * @param {array} items
1734 */
1735 this.ItemsSetSelectedValue = function(items, value){
1736 var l = items.length;
1737 for( var j = 0; j < l ; j++) {
1738 ThumbnailSelectionSet(items[j], value);
1739 }
1740 };
1741
1742 /**
1743 * Returns an array of selected items
1744 * @returns {Array}
1745 */
1746 this.ItemsSelectedGet = function(){
1747 var selectedItems = [];
1748 var l = G.I.length;
1749 for( var i = 0; i < l ; i++ ) {
1750 if( G.I[i].selected == true ) {
1751 selectedItems.push(G.I[i]);
1752 }
1753 }
1754 return selectedItems;
1755 };
1756
1757 /**
1758 * Returns the value of an option
1759 * @param {string} option
1760 * @returns {nanoGALLERY.G.O}
1761 */
1762 this.Get = function(option){
1763 return G.O[option];
1764 };
1765
1766 /**
1767 * Set a new value for a defined option
1768 * @param {string} option
1769 */
1770 this.Set = function(option, value){
1771 G.O[option] = value;
1772 switch( option ) {
1773 case 'thumbnailSelectable':
1774 ThumbnailSelectionClear();
1775 // refresh the displayed gallery
1776 GalleryRender( G.GOM.albumIdx );
1777 break;
1778 }
1779 };
1780
1781 /**
1782 * refresh the current gallery
1783 */
1784 this.Refresh = function() {
1785 // refresh the displayed gallery
1786 GalleryRender( G.GOM.albumIdx );
1787 };
1788 /**
1789 * resize the current gallery
1790 */
1791 this.Resize = function() {
1792 // resize the displayed gallery
1793 GalleryResize();
1794 };
1795
1796 /**
1797 * display one item (image or gallery)
1798 * itemID syntax:
1799 * - albumID --> display one album
1800 * - albumID/imageID --> display one image
1801 */
1802 this.DisplayItem = function( itemID ) {
1803 var IDs=parseIDs( itemID );
1804 if( IDs.imageID != '0' ) {
1805 DisplayPhoto( IDs.imageID, IDs.albumID );
1806 }
1807 else {
1808 DisplayAlbum( '-1', IDs.albumID );
1809 }
1810 };
1811
1812
1813
1814 var CountItemsToDisplay = function( gIdx ) {
1815 if( G.I[gIdx] == undefined ) { return 0; }
1816 var albumID = G.I[gIdx].GetID();
1817 var l = G.I.length;
1818 var cnt = 0;
1819 for( var idx = 0; idx < l; idx++ ) {
1820 var item = G.I[idx];
1821 if( item.isToDisplay(albumID) ) {
1822 cnt++;
1823 }
1824 }
1825 return cnt;
1826 }
1827 /**
1828 * Search in the displayed gallery (in thumbnails title)
1829 */
1830 this.Search = function( search ) {
1831 G.GOM.albumSearch = search.toUpperCase();
1832 var gIdx = G.GOM.albumIdx;
1833 GalleryRender( G.GOM.albumIdx );
1834 return CountItemsToDisplay( gIdx );
1835 };
1836
1837 /**
1838 * Search2 in title and tags - set search values
1839 */
1840 this.Search2 = function( searchTitle, searchTags ) {
1841 if( searchTitle != null && searchTitle != undefined ) {
1842 G.GOM.albumSearch = searchTitle.toUpperCase();
1843 }
1844 else {
1845 G.GOM.albumSearch = '';
1846 }
1847
1848 if( searchTags != null && searchTags != undefined ) {
1849 G.GOM.albumSearchTags = searchTags.toUpperCase();
1850 }
1851 else {
1852 G.GOM.albumSearchTags = '';
1853 }
1854 return CountItemsToDisplay( G.GOM.albumIdx );
1855 };
1856
1857 /**
1858 * Search2 - execute the search on title and tags
1859 */
1860 this.Search2Execute = function() {
1861 var gIdx = G.GOM.albumIdx;
1862 GalleryRender( G.GOM.albumIdx );
1863 return CountItemsToDisplay( gIdx );
1864 };
1865
1866
1867 /**
1868 * Destroy the current gallery
1869 */
1870 this.Destroy = function(){
1871 // alert('destroy');
1872 // var event = new Event('build');
1873 if( G.GOM.hammertime != null ) {
1874 G.GOM.hammertime.destroy();
1875 G.GOM.hammertime = null;
1876 }
1877 // G.GOM.userEvents.RemoveEvtListener();
1878 // G.GOM.userEvents=null;
1879 // G.VOM.userEvents.RemoveEvtListener();
1880 // G.VOM.userEvents=null;
1881 if( G.VOM.hammertime != null ) {
1882 G.VOM.hammertime.destroy();
1883 G.VOM.hammertime = null;
1884 }
1885 //ThumbnailHoverReInitAll();
1886
1887 // color scheme
1888 $('#ngycs_' + G.baseEltID).remove()
1889
1890 G.GOM.items = [];
1891 NGY2Item.New( G, G.i18nTranslations.breadcrumbHome, '', '0', '-1', 'album' );
1892 G.GOM.navigationBar.$newContent = null;
1893 G.$E.base.empty();
1894 G.$E.base.removeData();
1895 if( G.O.locationHash ) {
1896 jQuery(window).off('hashchange.nanogallery2.' + G.baseEltID);
1897 }
1898 jQuery(window).off('resize.nanogallery2.' + G.baseEltID);
1899 jQuery(window).off('orientationChange.nanogallery2.' + G.baseEltID);
1900 jQuery(window).off('scroll.nanogallery2.' + G.baseEltID);
1901 G.GOM.firstDisplay = false;
1902 };
1903
1904 /**
1905 * CloseViewer - close the media viewer
1906 */
1907 this.CloseViewer = function() {
1908 CloseInternalViewer(null);
1909 return false;
1910 };
1911
1912 /**
1913 * MinimizeToolbar - display the minimized lightbox main toolbar
1914 */
1915 this.MinimizeToolbar = function() {
1916 ViewerToolbarForVisibilityMin();
1917 return false;
1918 };
1919
1920 /**
1921 * MaximizeToolbar - display the maximized/standard lightbox main toolbar
1922 */
1923 this.MaximizeToolbar = function() {
1924 ViewerToolbarForVisibilityStd();
1925 return false;
1926 };
1927
1928 /**
1929 * PaginationPreviousPage - gallery paginate to previous page
1930 */
1931 this.PaginationPreviousPage = function() {
1932 paginationPreviousPage();
1933 return false;
1934 };
1935
1936
1937 /**
1938 * PaginationNextPage - gallery paginate to next page
1939 */
1940 this.PaginationNextPage = function() {
1941 paginationNextPage();
1942 return false;
1943 };
1944
1945
1946 /**
1947 * PaginationGotoPage - gallery paginate to specific page
1948 */
1949 this.PaginationGotoPage = function( page ) {
1950 var aIdx = G.$E.conPagin.data('galleryIdx');
1951 if( page > 1 ) { page--; }
1952 G.GOM.pagination.currentPage = page;
1953
1954 // scroll to top of gallery if not displayed
1955 if( !inViewportVert(G.$E.base, 0) ) {
1956 $('html, body').animate({scrollTop: G.$E.base.offset().top}, 200);
1957 }
1958 GalleryDisplayPart1( true );
1959 GalleryDisplayPart2( true );
1960 return false;
1961 };
1962
1963 /**
1964 * PaginationCountPages - gallery pagination - returns the number of pages
1965 */
1966 this.PaginationCountPages = function() {
1967 if( G.GOM.items.length == 0 ) { return 0; } // no thumbnail to display
1968
1969 var nbPages = Math.ceil((G.GOM.items[G.GOM.items.length - 1].row + 1) / G.galleryMaxRows.Get());
1970 return nbPages;
1971 };
1972
1973
1974
1975
1976 // throttle()
1977 // author: underscore.js - http://underscorejs.org/docs/underscore.html
1978 // Returns a function, that, when invoked, will only be triggered at most once during a given window of time.
1979 // Normally, the throttled function will run as much as it can, without ever going more than once per wait duration;
1980 // but if you�d like to disable the execution on the leading edge, pass {leading: false}.
1981 // To disable execution on the trailing edge, ditto.
1982 var throttle = function(func, wait, options) {
1983 var context, args, result;
1984 var timeout = null;
1985 var previous = 0;
1986 if (!options) options = {};
1987 var later = function() {
1988 previous = options.leading === false ? 0 : new Date().getTime();
1989 timeout = null;
1990 result = func.apply(context, args);
1991 if (!timeout) context = args = null;
1992 };
1993 return function() {
1994 var now = new Date().getTime();
1995 if (!previous && options.leading === false) previous = now;
1996 var remaining = wait - (now - previous);
1997 context = this;
1998 args = arguments;
1999 if (remaining <= 0 || remaining > wait) {
2000 if (timeout) {
2001 clearTimeout(timeout);
2002 timeout = null;
2003 }
2004 previous = now;
2005 result = func.apply(context, args);
2006 if (!timeout) context = args = null;
2007 } else if (!timeout && options.trailing !== false) {
2008 timeout = setTimeout(later, remaining);
2009 }
2010 return result;
2011 };
2012 };
2013
2014
2015 // DEBOUNCE
2016 // author: John Hann - http://unscriptable.com/2009/03/20/debouncing-javascript-methods/
2017 // execAsap - false means executing at the end of the detection period
2018 var debounce = function (func, threshold, execAsap) {
2019 var timeout;
2020 return function debounced () {
2021 var obj = this, args = arguments;
2022 function delayed () {
2023 if (!execAsap)
2024 func.apply(obj, args);
2025 timeout = null;
2026 };
2027
2028 if (timeout)
2029 clearTimeout(timeout);
2030 // clearRequestTimeout(timeout);
2031 else if (execAsap)
2032 func.apply(obj, args);
2033 timeout = setTimeout(delayed, threshold || 100);
2034 // timeout = requestTimeout(delayed, threshold || 100);
2035 };
2036 }
2037
2038
2039 // Double requestAnimationFrame
2040 window.ng_draf = function (cb) {
2041 return requestAnimationFrame(function() {
2042 window.requestAnimationFrame(cb)
2043 })
2044 }
2045
2046 // REQUESTTIMEOUT - replace SETTIMEOUT - https://gist.github.com/joelambert/1002116
2047 /**
2048 * Behaves the same as setTimeout except uses requestAnimationFrame() where possible for better performance
2049 * @param {function} fn The callback function
2050 * @param {int} delay The delay in milliseconds
2051 */
2052
2053 window.requestTimeout = function(fn, delay) {
2054 // if( !window.requestAnimationFrame &&
2055 // !window.webkitRequestAnimationFrame &&
2056 // !(window.mozRequestAnimationFrame && window.mozCancelRequestAnimationFrame) && // Firefox 5 ships without cancel support
2057 // !window.oRequestAnimationFrame &&
2058 // !window.msRequestAnimationFrame)
2059 return window.setTimeout(fn, delay);
2060
2061 var start = new Date().getTime(),
2062 handle = new Object();
2063
2064 function loop(){
2065 var current = new Date().getTime(),
2066 delta = current - start;
2067 delta = delay;
2068
2069 delta >= delay ? fn.call() : handle.value = requestAnimFrame(loop);
2070 };
2071
2072 handle.value = requestAnimFrame(loop);
2073 return handle;
2074 };
2075
2076
2077 // requestAnimationFrame() shim by Paul Irish
2078 // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
2079 window.requestAnimFrame = (function() {
2080 return window.requestAnimationFrame ||
2081 window.webkitRequestAnimationFrame ||
2082 window.mozRequestAnimationFrame ||
2083 window.oRequestAnimationFrame ||
2084 window.msRequestAnimationFrame ||
2085 function(/* function */ callback, /* DOMElement */ element){
2086 window.setTimeout(callback, 1000 / 60);
2087 };
2088 })();
2089
2090
2091 // CLEARREQUESTTIMEOUT - to replace CLEARTIMEOUT - https://gist.github.com/joelambert/1002116
2092 /**
2093 * Behaves the same as clearTimeout except uses cancelRequestAnimationFrame() where possible for better performance
2094 * @param {int|object} fn The callback function
2095 */
2096 window.clearRequestTimeout = function(handle) {
2097 window.cancelAnimationFrame ? window.cancelAnimationFrame(handle.value) :
2098 window.webkitCancelAnimationFrame ? window.webkitCancelAnimationFrame(handle.value) :
2099 window.webkitCancelRequestAnimationFrame ? window.webkitCancelRequestAnimationFrame(handle.value) : /* Support for legacy API */
2100 window.mozCancelRequestAnimationFrame ? window.mozCancelRequestAnimationFrame(handle.value) :
2101 window.oCancelRequestAnimationFrame ? window.oCancelRequestAnimationFrame(handle.value) :
2102 window.msCancelRequestAnimationFrame ? window.msCancelRequestAnimationFrame(handle.value) :
2103 clearTimeout(handle);
2104 };
2105
2106
2107
2108 /*
2109 ** Global data for this nanogallery2 instance
2110 **/
2111 var G=this;
2112 G.I = []; // gallery items
2113 G.Id = []; // gallery items
2114 G.O = null; // user options
2115 G.baseEltID = null; // ID of the base element
2116 G.$E = {
2117 base: null, // base element
2118 conTnParent: null, // $g_containerThumbnailsParent
2119 conLoadingB: null, // loading bar - nanoGalleryLBarOff
2120 conConsole: null, // console for error messages
2121 conNavigationBar: null, // gallery navigation bar
2122 conTnBottom: null // container on the bottom of the gallery
2123 };
2124 G.shoppingCart = [];
2125 G.layout = { // Layout informations
2126 internal : true,
2127 engine : '',
2128 support : { rows: false },
2129 prerequisite : { imageSize: false },
2130 SetEngine: function() {
2131
2132 if( G.layout.internal ) {
2133 if( G.tn.settings.width[G.GOM.curNavLevel][G.GOM.curWidth] == 'auto' || G.tn.settings.width[G.GOM.curNavLevel][G.GOM.curWidth] == '' ) {
2134 // do not use getH() / getW() here!
2135 G.layout.engine = 'JUSTIFIED';
2136 G.layout.support.rows = true;
2137 G.layout.prerequisite.imageSize = true;
2138 return;
2139 }
2140 if( G.tn.settings.height[G.GOM.curNavLevel][G.GOM.curWidth] == 'auto' || G.tn.settings.height[G.GOM.curNavLevel][G.GOM.curWidth] == '' ) {
2141 // do not use getH() / getW() here!
2142 G.layout.engine = 'CASCADING';
2143 G.layout.support.rows = false;
2144 G.layout.prerequisite.imageSize = true;
2145 return;
2146 }
2147
2148 if( G.tn.settings.getMosaic() != null ) {
2149 G.layout.engine = 'MOSAIC';
2150 G.layout.support.rows = true;
2151 G.layout.prerequisite.imageSize = false;
2152 return;
2153 }
2154
2155 G.layout.engine='GRID';
2156 G.layout.support.rows=true;
2157 // if( G.tn.opt.Get('crop') === true ) {
2158 // G.layout.prerequisite.imageSize = true;
2159 // }
2160 // else {
2161 G.layout.prerequisite.imageSize = false;
2162 // }
2163 }
2164 }
2165 };
2166 G.galleryResizeEventEnabled = false;
2167 G.galleryMaxRows = { l1: 0, lN: 0,
2168 Get: function() {
2169 return G.galleryMaxRows[G.GOM.curNavLevel];
2170 }
2171 };
2172 G.galleryMaxItems = { l1: 0, lN: 0,
2173 Get: function() {
2174 return G.galleryMaxItems[G.GOM.curNavLevel];
2175 }
2176 };
2177 G.galleryFilterTags = { l1: 0, lN: 0,
2178 Get: function() {
2179 return G.galleryFilterTags[G.GOM.curNavLevel];
2180 }
2181 };
2182 G.galleryDisplayMode = { l1: 'FULLCONTENT', lN: 'FULLCONTENT',
2183 Get: function() {
2184 return G.galleryDisplayMode[G.GOM.curNavLevel];
2185 }
2186 };
2187 G.galleryLastRowFull = { l1: false, lN: false,
2188 Get: function() {
2189 return G.galleryLastRowFull[G.GOM.curNavLevel];
2190 }
2191 };
2192 G.gallerySorting = { l1: '', lN: '',
2193 Get: function() {
2194 return G.gallerySorting[G.GOM.curNavLevel];
2195 }
2196 };
2197 G.galleryDisplayTransition = { l1: 'none', lN: 'none',
2198 Get: function() {
2199 return G.galleryDisplayTransition[G.GOM.curNavLevel];
2200 }
2201 };
2202 G.galleryDisplayTransitionDuration = { l1: 500, lN: 500,
2203 Get: function() {
2204 return G.galleryDisplayTransitionDuration[G.GOM.curNavLevel];
2205 }
2206 };
2207 G.$currentTouchedThumbnail = null;
2208
2209 // ##### GENERAL THUMBNAILS PROPERTIES -->
2210 G.tn = {
2211 // levell specific options
2212 opt: {
2213 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 },
2214 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 },
2215 Get: function(opt) {
2216 return G.tn.opt[G.GOM.curNavLevel][opt];
2217 }
2218 },
2219 scale: 1, // image scale depending of the hover effect
2220 borderWidth: 0, // thumbnail container border width
2221 borderHeight: 0, // thumbnail container border height
2222 labelHeight: { // in case label on bottom, otherwise always=0
2223 l1: 0, lN: 0,
2224 get: function() {
2225 return G.tn.labelHeight[G.GOM.curNavLevel];
2226 }
2227 },
2228 defaultSize: { // default thumbnail size
2229 // label height is not included
2230 width: { l1 : { xs:0, sm:0, me:0, la:0, xl:0 }, lN : { xs:0, sm:0, me:0, la:0, xl:0 } },
2231 height: { l1 : { xs:0, sm:0, me:0, la:0, xl:0 }, lN : { xs:0, sm:0, me:0, la:0, xl:0 } },
2232 getWidth: function() {
2233 return G.tn.defaultSize.width[G.GOM.curNavLevel][G.GOM.curWidth];
2234 },
2235 getOuterWidth: function() { // width border included
2236 return G.tn.defaultSize.width[G.GOM.curNavLevel][G.GOM.curWidth]+G.tn.borderWidth*2;
2237 },
2238 getHeight: function() {
2239 return G.tn.defaultSize.height[G.GOM.curNavLevel][G.GOM.curWidth];
2240 },
2241 getOuterHeight: function() { // height, border included
2242 return G.tn.defaultSize.height[G.GOM.curNavLevel][G.GOM.curWidth]+G.tn.borderHeight*2;
2243 }
2244 },
2245 settings: { // user defined width/height of the image to display depending on the screen size
2246 width: { l1 : { xs:0, sm:0, me:0, la:0, xl:0, xsc:'u', smc:'u', mec:'u', lac:'u', xlc:'u' },
2247 lN : { xs:0, sm:0, me:0, la:0, xl:0, xsc:'u', smc:'u', mec:'u', lac:'u', xlc:'u' } },
2248 height: { l1 : { xs:0, sm:0, me:0, la:0, xl:0, xsc:'u', smc:'u', mec:'u', lac:'u', xlc:'u' },
2249 lN : { xs:0, sm:0, me:0, la:0, xl:0, xsc:'u', smc:'u', mec:'u', lac:'u', xlc:'u' } },
2250 getH: function(l, w) {
2251 var cl = (l == undefined ? G.GOM.curNavLevel : l);
2252 var cw = (w == undefined ? G.GOM.curWidth : w);
2253 if( G.layout.engine == 'MOSAIC' ) {
2254 return G.tn.settings.height[cl][cw] * G.tn.settings.mosaic[cl+'Factor']['h'][cw];
2255 }
2256 else {
2257 return G.tn.settings.height[cl][cw];
2258 }
2259 },
2260 getW: function(l, w) {
2261 var cl = (l == undefined ? G.GOM.curNavLevel : l);
2262 var cw = (w == undefined ? G.GOM.curWidth : w);
2263 if( G.layout.engine == 'MOSAIC' ) {
2264 return G.tn.settings.width[cl][cw] * G.tn.settings.mosaic[cl+'Factor']['w'][cw];
2265 }
2266 else {
2267 return G.tn.settings.width[cl][cw];
2268 // return G.tn.settings.width[G.GOM.curNavLevel][G.GOM.curWidth];
2269 }
2270 },
2271 mosaic: { l1 : { xs: null, sm: null, me: null, la: null, xl: null },
2272 lN : { xs: null, sm: null, me: null, la: null, xl: null },
2273 l1Factor : { h :{ xs: 1, sm: 1, me: 1, la: 1, xl: 1 }, w :{ xs: 1, sm: 1, me: 1, la: 1, xl: 1 }},
2274 lNFactor : { h :{ xs: 1, sm: 1, me: 1, la: 1, xl: 1 }, w :{ xs: 1, sm: 1, me: 1, la: 1, xl: 1 }}
2275 },
2276 getMosaic: function() {
2277 return G.tn.settings.mosaic[G.GOM.curNavLevel][G.GOM.curWidth];
2278 },
2279 mosaicCalcFactor: function(l, w) {
2280 // retrieve max size multiplicator
2281 var maxW = 1;
2282 var maxH = 1;
2283 for( var n = 0; n < G.tn.settings.mosaic[l][w].length; n++ ) {
2284 maxW = Math.max(maxW, G.tn.settings.mosaic[l][w][n]['w']);
2285 maxH = Math.max(maxH, G.tn.settings.mosaic[l][w][n]['h']);
2286 }
2287 G.tn.settings.mosaic[l + 'Factor']['h'][w] = maxH;
2288 G.tn.settings.mosaic[l + 'Factor']['w'][w] = maxW;
2289 }
2290 },
2291 // thumbnail hover effects
2292 hoverEffects : {
2293 std : [],
2294 level1: [],
2295 get: function() {
2296 if( G.GOM.curNavLevel == 'l1' && G.tn.hoverEffects.level1.length !== 0 ) {
2297 return G.tn.hoverEffects.level1;
2298 }
2299 else {
2300 return G.tn.hoverEffects.std;
2301 }
2302 }
2303 },
2304 // thumbnail init
2305 buildInit : {
2306 std : [],
2307 level1: [],
2308 get: function() {
2309 if( G.GOM.curNavLevel == 'l1' && G.tn.buildInit.level1.length !== 0 ) {
2310 return G.tn.buildInit.level1;
2311 }
2312 else {
2313 return G.tn.buildInit.std;
2314 }
2315 }
2316 },
2317 // thumbnail toolbars
2318 toolbar: {
2319 album : { topLeft : '', topRight: '', bottomLeft: '', bottomRight: '' },
2320 image : { topLeft : '', topRight: '', bottomLeft: '', bottomRight: '' },
2321 albumUp : { topLeft : '', topRight: '', bottomLeft: '', bottomRight: '' },
2322 get: function( item ) {
2323 return G.tn.toolbar[item.kind];
2324 },
2325 },
2326 style: {
2327 // inline CSS
2328 l1 : { label: '', title: '', desc: '' },
2329 lN : { label: '', title: '', desc: '' },
2330 getTitle : function() {
2331 return ('style="' + G.tn.style[G.GOM.curNavLevel].title + '"');
2332 },
2333 getDesc : function() {
2334 return ('style="' + G.tn.style[G.GOM.curNavLevel].desc + '"');
2335 },
2336 getLabel: function() {
2337 var s='style="'+ G.tn.style[G.GOM.curNavLevel].label;
2338 s+= (G.O.RTL ? '"direction:RTL;"' :'');
2339 s+='"';
2340 return s;
2341 }
2342 }
2343 };
2344 G.scrollTimeOut = 0;
2345 G.i18nTranslations = {'paginationPrevious':'Previous', 'paginationNext':'Next', 'breadcrumbHome':'List of Albums', 'thumbnailImageTitle':'', 'thumbnailAlbumTitle':'', 'thumbnailImageDescription':'', 'thumbnailAlbumDescription':'' };
2346 G.emptyGif = 'data:image/gif;base64,R0lGODlhEAAQAIAAAP///////yH5BAEKAAEALAAAAAAQABAAAAIOjI+py+0Po5y02ouzPgUAOw==';
2347 G.CSStransformName = FirstSupportedPropertyName(["transform", "msTransform", "MozTransform", "WebkitTransform", "OTransform"]);
2348 // G.CSSfilterName = FirstSupportedPropertyName(["filter", "WebkitFilter"]);
2349 G.CSStransformStyle = FirstSupportedPropertyName(["transformStyle", "msTransformStyle", "MozTransformStyle", "WebkitTransformStyle", "OTransformStyle"]);
2350 G.CSSperspective = FirstSupportedPropertyName(["perspective", "msPerspective", "MozPerspective", "WebkitPerspective", "OPerspective"]);
2351 G.CSSbackfaceVisibilityName = FirstSupportedPropertyName(["backfaceVisibility", "msBackfaceVisibility", "MozBackfaceVisibility", "WebkitBackfaceVisibility", "OBackfaceVisibility"]);
2352 G.CSStransitionName = FirstSupportedPropertyName(["transition", "msTransition", "MozTransition", "WebkitTransition", "OTransition"]);
2353 G.CSSanimationName = FirstSupportedPropertyName(["animation", "msAnimation", "MozAnimation", "WebkitAnimation", "OAnimation"]);
2354 G.GalleryResizeThrottled = throttle(GalleryResize, 30, {leading: false});
2355
2356 G.blackList = null; // album white list
2357 G.whiteList = null; // album black list
2358 G.albumList = []; // album list
2359 G.locationHashLastUsed = '';
2360 G.custGlobals = {};
2361 G.touchAutoOpenDelayTimerID = 0;
2362 G.i18nLang = '';
2363 G.timeLastTouchStart = 0;
2364 G.custGlobals = {};
2365 G.markupOrApiProcessed = false;
2366
2367 //------------------------
2368 //--- Gallery Object Model
2369 G.GOM = {
2370 albumIdx : -1, // index (in G.I) of the currently displayed album
2371 clipArea : { top: 0, height: 0 }, // area of the GOM to display on screen
2372 displayArea : { width: 0 , height: 0 }, // size of the GOM area (=used area, not available area)
2373 displayAreaLast : { width: 0 , height: 0 }, // previous size of the GOM area
2374 displayedMoreSteps : 0, // current number of displayed steps (moreButton mode)
2375 items: [], // current items of the GOMS
2376 $imgPreloader: [],
2377 thumbnails2Display: [],
2378 itemsDisplayed : 0, // number of currently displayed thumbnails
2379 firstDisplay : true,
2380 firstDisplayTime : 0, // in conjunction with galleryRenderDelay
2381 navigationBar : { // content of the navigation bar (for breadcrumb and filter tags)
2382 displayed: false,
2383 $newContent: ''
2384 },
2385 cache : { // cached data
2386 viewport: null,
2387 containerOffset: null,
2388 areaWidth: 100 // available area width
2389 },
2390 nbSelected : 0, // number of selected items
2391 pagination : { currentPage: 0 }, // pagination data
2392 panThreshold: 60, // threshold value (in pixels) to block horizontal pan/swipe
2393 panYOnly: false, // threshold value reach -> definitively block horizontal pan until end of pan
2394 lastFullRow : -1, // number of the last row without holes
2395 lastDisplayedIdx: -1, // used to display the counter of not displayed items
2396 displayInterval : { from: 0, len: 0 },
2397 userEvents: null,
2398 hammertime: null,
2399 curNavLevel: 'l1', // current navigation level (l1 or LN)
2400 curWidth: 'me',
2401 albumSearch: '', // current search string -> title (used to filter the thumbnails on screen)
2402 albumSearchTags: '', // current search string -> tags
2403 lastZIndex: 0, // used to put a thumbnail on top of all others (for exemple for scale hover effect)
2404 lastRandomValue: 0,
2405 slider : { // slider on last thumbnail
2406 hostIdx: -1, // idx of the thumbnail hosting the slider
2407 hostItem: null, // item hosting the slider
2408 currentIdx: 0, // idx of the current displayed item
2409 nextIdx: 0, // idx of the next item to display in the slider
2410 timerID: 0,
2411 tween: null // tranistion tween instance
2412 },
2413 NGY2Item: function( idx ) { // returns a NGY2Item or null if it does not exist
2414 if( G.GOM.items[idx] == undefined || G.GOM.items[idx] == null ) { return null; }
2415 var i = G.GOM.items[idx].thumbnailIdx;
2416 return G.I[i];
2417 },
2418 // One GOM item (thumbnail)
2419 // function GTn(index, width, height) {
2420 GTn: function(index, width, height) {
2421 this.thumbnailIdx = index;
2422 this.width = 0; // thumbnail width
2423 this.height = 0; // thumbnail height
2424 this.top = 0; // position: top
2425 this.left = 0; // position: left
2426 this.row = 0; // position: row number
2427 this.imageWidth = width; // image width
2428 this.imageHeight = height; // image height
2429 this.resizedContentWidth = 0;
2430 this.resizedContentHeight = 0;
2431 this.displayed = false;
2432 this.neverDisplayed = true;
2433 this.inDisplayArea = false;
2434 }
2435 };
2436
2437
2438 //------------------------
2439 //--- Viewer Object Model
2440
2441 G.VOM = {
2442 viewerDisplayed: false, // is the viewer currently displayed
2443 viewerIsFullscreen: false, // viewer in fullscreen mode
2444 infoDisplayed: false, // is the info box displayed
2445 toolbarsDisplayed: true, // the toolbars are displayed
2446 toolsHide: null,
2447 saveOverflowX: 'visible', // store the value to restore it back after viewer is closed
2448 saveOverflowY: 'visible',
2449 zoom : {
2450 posX: 0, // position to center zoom in/out
2451 posY: 0,
2452 userFactor: 1, // user zoom factor (applied to the baseZoom factor)
2453 isZooming: false
2454 },
2455 padding: { H: 0, V: 0 }, // padding for the image
2456 window: { lastWidth: 0, lastHeight: 0 },
2457 $cont: null, // viewer container
2458 $viewer: null,
2459 $toolbar: null, // viewerToolbar
2460 $toolbarTL: null, // viewer toolbar on top left
2461 $toolbarTR: null, // viewer toolbar on top right
2462 $content: null, // viewer content
2463
2464 $mediaPrevious: null, // previous image
2465 $mediaCurrent: null, // current image
2466 $mediaNext: null, // next image
2467 toolbarMode: 'std', // current toolbar mode (standard, minimized)
2468 playSlideshow : false, // slide show mode status
2469 playSlideshowTimerID: 0, // slideshow mode time
2470 slideshowDelay: 3000, // slideshow mode - delay before next image
2471 albumID: -1,
2472 currItemIdx: -1,
2473 viewerMediaIsChanged: false, // media display is currently modified
2474 items: [], // current list of images to be managed by the viewer
2475 NGY2Item: function( n ) { // returns a NGY2Item
2476 switch( n ) {
2477 case -1: // previous
2478 var idx=this.IdxPrevious();
2479 return G.I[this.items[idx].ngy2ItemIdx];
2480 break;
2481 case 1: // next
2482 var idx=this.IdxNext();
2483 return G.I[this.items[idx].ngy2ItemIdx];
2484 break;
2485 case 0: // current
2486 default:
2487 return G.I[this.items[G.VOM.currItemIdx].ngy2ItemIdx];
2488 break;
2489 }
2490 },
2491 IdxNext: function() {
2492 var n = 0;
2493 if( G.VOM.currItemIdx != (G.VOM.items.length-1) ) {
2494 n = G.VOM.currItemIdx + 1;
2495 }
2496 return n;
2497 },
2498 IdxPrevious: function() {
2499 var n = G.VOM.currItemIdx - 1;
2500 if( G.VOM.currItemIdx == 0 ) {
2501 n = G.VOM.items.length - 1;
2502 }
2503 return n;
2504 },
2505 userEvents: null, // user events management
2506 hammertime: null, // hammer.js manager
2507 swipePosX: 0, // current horizontal swip position
2508 panPosX: 0, // position for manual pan
2509 panPosY: 0,
2510 panThreshold: 60, // threshold value (in pixels) to block vertical pan
2511 panXOnly: false, // threshold value reach -> definitively block vertical pan until end of pan
2512 viewerTheme: '',
2513 timeImgChanged: 0,
2514 ImageLoader: {
2515 // fires a callback when image size is know (during download)
2516 // inspired by ROB - http://stackoverflow.com/users/226507/rob
2517 maxChecks: 1000,
2518 list: [],
2519 intervalHandle : null,
2520
2521 loadImage : function (callback, ngitem) {
2522 if( ngitem.mediaKind != 'img' ) { return; } // ignore - only for images
2523 var img = new Image ();
2524 img.src = ngitem.responsiveURL();
2525 if (img.width && img.height) {
2526 callback (img.width, img.height, ngitem, 0);
2527 }
2528 else {
2529 var obj = {image: img, url: ngitem.responsiveURL(), ngitem: ngitem, callback: callback, checks: 1};
2530 var i;
2531 for (i=0; i < this.list.length; i++) {
2532 if (this.list[i] == null)
2533 break;
2534 }
2535 this.list[i] = obj;
2536 if (!this.intervalHandle)
2537 this.intervalHandle = setInterval(this.interval, 50);
2538 }
2539 },
2540
2541 // called by setInterval
2542 interval : function () {
2543 var count = 0;
2544 var list = G.VOM.ImageLoader.list, item;
2545 for (var i=0; i<list.length; i++) {
2546 item = list[i];
2547 if (item != null) {
2548 if (item.image.width && item.image.height) {
2549 G.VOM.ImageLoader.list[i] = null;
2550 item.callback (item.image.width, item.image.height, item.ngitem, item.checks);
2551 }
2552 else if (item.checks > G.VOM.ImageLoader.maxChecks) {
2553 G.VOM.ImageLoader.list[i] = null;
2554 item.callback (0, 0, item.ngitem, item.checks);
2555 }
2556 else {
2557 count++;
2558 item.checks++;
2559 }
2560 }
2561 }
2562 if (count == 0) {
2563 G.VOM.ImageLoader.list = [];
2564 clearInterval (G.VOM.ImageLoader.intervalHandle);
2565 delete G.VOM.ImageLoader.intervalHandle;
2566 }
2567 }
2568 }
2569 }
2570 // One VOM item (image)
2571 function VImg(index) {
2572 this.$e = null;
2573 this.ngy2ItemIdx = index;
2574 this.mediaNumber = 0;
2575 this.posX = 0; // to center the element
2576 this.posY = 0;
2577 }
2578
2579
2580 //------------------------
2581 //--- popup
2582 G.popup = {
2583 isDisplayed: false,
2584 $elt: null,
2585 close: function() {
2586 if( this.$elt != null ) {
2587 var tweenable = new NGTweenable();
2588 tweenable.tween({
2589 from: { opacity:1 },
2590 to: { opacity:0 },
2591 attachment: { t: this },
2592 easing: 'easeInOutSine',
2593 duration: 100,
2594 step: function (state, att) {
2595 if( att.t.$elt != null ) {
2596 att.t.$elt.css('opacity',state.opacity);
2597 }
2598 },
2599 finish: function (state, att) {
2600 if( att.t.$elt != null ) {
2601 att.t.$elt.remove();
2602 att.t.$elt=null;
2603 }
2604 att.t.isDisplayed=false;
2605 }
2606 });
2607 }
2608 }
2609 }
2610
2611
2612 // Color schemes - Gallery
2613 // gadrient generator: https://www.grabient.com/
2614 G.galleryTheme_dark = {
2615 navigationBar : { background: 'none', borderTop: '', borderBottom: '', borderRight: '', borderLeft: '' },
2616 navigationBreadcrumb : { background: '#111', color: '#fff', colorHover: '#ccc', borderRadius: '4px' },
2617 navigationFilter : { color: '#ddd', background: '#111', colorSelected: '#fff', backgroundSelected: '#111', borderRadius: '4px' },
2618 thumbnail : { background: '#444', backgroundImage: 'linear-gradient(315deg, #111 0%, #445 90%)', borderColor: '#000', labelOpacity : 1, labelBackground: 'rgba(34, 34, 34, 0)', titleColor: '#fff', titleBgColor: 'transparent', titleShadow: '', descriptionColor: '#ccc', descriptionBgColor: 'transparent', descriptionShadow: '', stackBackground: '#aaa' },
2619 thumbnailIcon : { padding: '5px', color: '#fff' },
2620 pagination : { background: '#181818', backgroundSelected: '#666', color: '#fff', borderRadius: '2px', shapeBorder: '3px solid #666', shapeColor: '#444', shapeSelectedColor: '#aaa'}
2621 };
2622
2623 G.galleryTheme_light = {
2624 navigationBar : { background: 'none', borderTop: '', borderBottom: '', borderRight: '', borderLeft: '' },
2625 navigationBreadcrumb : { background: '#eee', color: '#000', colorHover: '#333', borderRadius: '4px' },
2626 navigationFilter : { background: '#eee', color: '#222', colorSelected: '#000', backgroundSelected: '#eee', borderRadius: '4px' },
2627 thumbnail : { background: '#444', backgroundImage: 'linear-gradient(315deg, #111 0%, #445 90%)', borderColor: '#000', labelOpacity : 1, labelBackground: 'rgba(34, 34, 34, 0)', titleColor: '#fff', titleBgColor: 'transparent', titleShadow: '', descriptionColor: '#ccc', descriptionBgColor: 'transparent', descriptionShadow: '', stackBackground: '#888' },
2628 thumbnailIcon : { padding: '5px', color: '#fff' },
2629 pagination : { background: '#eee', backgroundSelected: '#aaa', color: '#000', borderRadius: '2px', shapeBorder: '3px solid #666', shapeColor: '#444', shapeSelectedColor: '#aaa'}
2630 };
2631
2632 // Color schemes - lightbox
2633 G.viewerTheme_dark = {
2634 background: '#000',
2635 imageBorder: 'none',
2636 imageBoxShadow: 'none',
2637 barBackground: 'rgba(4, 4, 4, 0.2)',
2638 barBorder: '0px solid #111',
2639 barColor: '#eee',
2640 barDescriptionColor: '#aaa'
2641 };
2642 G.viewerTheme_border = {
2643 background: 'rgba(1, 1, 1, 0.75)',
2644 imageBorder: '4px solid #f8f8f8',
2645 imageBoxShadow: '#888 0px 0px 20px',
2646 barBackground: 'rgba(4, 4, 4, 0.2)',
2647 barBorder: '0px solid #111',
2648 barColor: '#eee',
2649 barDescriptionColor: '#aaa'
2650 };
2651 G.viewerTheme_light = {
2652 background: '#f8f8f8',
2653 imageBorder: 'none',
2654 imageBoxShadow: 'none',
2655 barBackground: 'rgba(4, 4, 4, 0.7)',
2656 barBorder: '0px solid #111',
2657 barColor: '#eee',
2658 barDescriptionColor: '#aaa'
2659 };
2660
2661
2662
2663 // shortcut with G context to NGY2TOOLS
2664 // var NanoAlert = NGY2Tools.NanoAlert.bind(G);
2665 // var NanoConsoleLog = NGY2Tools.NanoConsoleLog.bind(G);
2666 var NanoAlert = NGY2Tools.NanoAlert;
2667 var NanoConsoleLog = NGY2Tools.NanoConsoleLog;
2668
2669
2670 /** @function initiateGallery2 */
2671 this.initiateGallery2 = function( element, params ) {
2672
2673 // GLOBAL OPTIONS
2674 G.O = params;
2675 // Base element
2676 G.$E.base = jQuery(element);
2677 G.baseEltID = G.$E.base.attr('id');
2678 if( G.baseEltID == undefined ) {
2679 // set a default ID to the root container
2680 G.baseEltID='my_nanogallery';
2681 G.$E.base.attr('id', G.baseEltID)
2682 }
2683 G.O.$markup = [];
2684 DefineVariables();
2685 SetPolyFills();
2686 BuildSkeleton();
2687 G.GOM.firstDisplayTime=Date.now();
2688
2689 SetGlobalEvents();
2690
2691 // check if only one specific album will be used
2692 var albumToDisplay = G.O.album;
2693 if( albumToDisplay == '' && G.O.photoset != '' ) {
2694 albumToDisplay = G.O.photoset;
2695 G.O.album = G.O.photoset;
2696 }
2697 if( albumToDisplay != '' ) {
2698 G.O.displayBreadcrumb = false; // no breadcrumb since only 1 album
2699 if( albumToDisplay.toUpperCase() != 'NONE' ) {
2700 // open specific album
2701
2702 var p=albumToDisplay.indexOf('&authkey=');
2703 if( p == -1 ) {
2704 p=albumToDisplay.indexOf('?authkey=');
2705 }
2706 if( p > 0 ) {
2707 // privat album with authkey
2708 G.O.locationHash=false; // disable hash location for hidden/privat albums --> impossible to handle
2709 var albumID=albumToDisplay.substring(0,p);
2710 var opt=albumToDisplay.substring(p);
2711 if( opt.indexOf('Gv1sRg') == -1 ) {
2712 opt = '&authkey=Gv1sRg'+opt.substring(9);
2713 }
2714 var newItem = NGY2Item.New( G, '', '', albumID, '-1', 'album' );
2715 newItem.authkey = opt;
2716 DisplayAlbum('-1', albumID);
2717 }
2718 else {
2719 // open a public album
2720 if( G.O.kind == "nano_photos_provider2") {
2721 if( albumToDisplay == decodeURIComponent(albumToDisplay)) {
2722 // album ID must be encoded
2723 albumToDisplay = encodeURIComponent(albumToDisplay);
2724 G.O.album = albumToDisplay;
2725 }
2726 }
2727 NGY2Item.New( G, '', '', albumToDisplay, '-1', 'album' );
2728 if( !ProcessLocationHash() ) {
2729 DisplayAlbum('-1', albumToDisplay);
2730 }
2731 }
2732 return;
2733 }
2734 }
2735
2736 // use full content
2737 // add base album
2738 NGY2Item.New( G, G.i18nTranslations.breadcrumbHome, '', '0', '-1', 'album' );
2739
2740 processStartOptions();
2741
2742 }
2743
2744
2745 /** @function processStartOptions */
2746 function processStartOptions() {
2747 // open image or album
2748 // 1. load hidden albums
2749 // 2. check if location hash set (deep linking)
2750 // 3. check openOnStart parameter
2751 // 4. open root album (ID=-1)
2752
2753 // hidden/private albums are loaded on plugin start (Picasa) --> no more available in Google Photos
2754 // if( G.albumListHidden.length > 0 ) {
2755 // jQuery.nanogallery2['data_'+G.O.kind](G, 'GetHiddenAlbums', G.albumListHidden, processStartOptionsPart2);
2756 // return;
2757 //}
2758
2759 if( !ProcessLocationHash() ) {
2760 processStartOptionsPart2();
2761 }
2762 }
2763
2764 /** @function processStartOptionsPart2 */
2765 function processStartOptionsPart2() {
2766
2767 // Check location hash + start parameters -> determine what to do on start
2768 // openOnStart parameter
2769 if( G.O.openOnStart != '' ) {
2770 var IDs = parseIDs(G.O.openOnStart);
2771 if( IDs.imageID != '0' ) {
2772 DisplayPhoto(IDs.imageID, IDs.albumID);
2773 }
2774 else {
2775 DisplayAlbum('-1', IDs.albumID);
2776 }
2777 }
2778 else {
2779 // open root album (ID = -1)
2780 DisplayAlbum('-1', 0);
2781 }
2782 }
2783
2784 // Parse string to extract albumID and imageID (format albumID/imageID)
2785 function parseIDs( IDs ) {
2786 var r={ albumID: '0', imageID: '0' };
2787
2788 var t=IDs.split('/');
2789 if( t.length > 0 ) {
2790 r.albumID = t[0];
2791 if( t.length > 1 ) {
2792 r.imageID = t[1];
2793 }
2794 }
2795 return r;
2796 }
2797
2798
2799 /** @function DisplayAlbum */
2800 function DisplayAlbum( imageID, albumID ) {
2801 // close viewer if already displayed
2802 if( G.VOM.viewerDisplayed ) {
2803 CloseInternalViewer(null);
2804 }
2805
2806 // set current navigation level (l1 or lN)
2807 var albumIdx = NGY2Item.GetIdx(G, albumID);
2808 G.GOM.curNavLevel = 'lN';
2809 if( albumIdx == 0 ) {
2810 G.GOM.curNavLevel = 'l1';
2811 }
2812 G.layout.SetEngine();
2813 G.galleryResizeEventEnabled = false;
2814
2815 if( albumIdx == -1 ) {
2816 NGY2Item.New( G, '', '', albumID, '0', 'album' ); // create empty album
2817 albumIdx = G.I.length - 1;
2818 }
2819
2820 if( !G.I[albumIdx].contentIsLoaded ) {
2821 // get content of the album if not already loaded
2822 AlbumGetContent( albumID, DisplayAlbum, imageID, albumID );
2823 return;
2824 }
2825
2826 ThumbnailSelectionClear();
2827
2828 G.GOM.pagination.currentPage = 0;
2829 SetLocationHash( albumID, '' );
2830 GalleryRender( albumIdx );
2831
2832 }
2833
2834
2835 //----- manage the bottom area of the gallery -> "pagination" or "more button"
2836 function GalleryBottomManage() {
2837
2838 switch( G.galleryDisplayMode.Get() ) {
2839 case 'PAGINATION':
2840 if( G.layout.support.rows && G.galleryMaxRows.Get() > 0 ) {
2841 ManagePagination( G.GOM.albumIdx );
2842 }
2843 break;
2844 case 'MOREBUTTON':
2845 G.$E.conTnBottom.off('click');
2846 var nb = G.GOM.items.length-G.GOM.itemsDisplayed;
2847 if( nb == 0 ) {
2848 G.$E.conTnBottom.empty();
2849 }
2850 else {
2851 G.$E.conTnBottom.html('<div class="nGY2GalleryMoreButton"><div class="nGY2GalleryMoreButtonAnnotation">+'+nb+' ' + G.O.icons.galleryMoreButton +'</div></div>');
2852 G.$E.conTnBottom.on('click', function(e) {
2853 G.GOM.displayedMoreSteps++;
2854 GalleryResize();
2855 });
2856 }
2857 break;
2858 case 'FULLCONTENT':
2859 default:
2860 break;
2861 }
2862 }
2863
2864
2865 // add one album/folder to the breadcrumb
2866 function breadcrumbAdd( albumIdx ) {
2867
2868 var ic='';
2869 if( !G.O.breadcrumbHideIcons ) {
2870 ic=G.O.icons.breadcrumbAlbum;
2871 if( albumIdx == 0 ) {
2872 ic=G.O.icons.breadcrumbHome;
2873 }
2874 }
2875 var $newDiv =jQuery('<div class="oneItem">'+ic + G.I[albumIdx].title+'</div>').appendTo(G.GOM.navigationBar.$newContent.find('.nGY2Breadcrumb'));
2876 if( G.O.breadcrumbOnlyCurrentLevel ) {
2877 // link to parent folder (only 1 level is displayed in the breadcrumb)
2878 if( albumIdx == 0 ) {
2879 // no parent level -> stay on current one
2880 jQuery($newDiv).data('albumID','0');
2881 }
2882 else {
2883 jQuery($newDiv).data('albumID',G.I[albumIdx].albumID);
2884 }
2885 }
2886 else {
2887 // link to current folder
2888 jQuery($newDiv).data('albumID',G.I[albumIdx].GetID());
2889 }
2890 $newDiv.click(function() {
2891 var cAlbumID=jQuery(this).data('albumID');
2892 DisplayAlbum('-1', cAlbumID);
2893 return;
2894 });
2895 }
2896
2897 // add one separator to breadcrumb
2898 function breadcrumbAddSeparator( lastAlbumID ) {
2899 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'));
2900 jQuery($newSep).data('albumIdx',lastAlbumID);
2901 $newSep.click(function() {
2902 var sepAlbumIdx=jQuery(this).data('albumIdx');
2903 DisplayAlbum('-1', G.I[sepAlbumIdx].GetID());
2904 return;
2905 });
2906 }
2907
2908
2909
2910 // Manage the gallery toolbar (breadcrumb + tag filter)
2911 function GalleryNavigationBar( albumIdx ) {
2912
2913 // Title + background image
2914 // var bgImage='';
2915 // var l=G.I.length;
2916 // var albumID = G.I[albumIdx].GetID();
2917 // for( var idx=0; idx<l ; idx++) {
2918 // var item=G.I[idx];
2919 // if( item.kind == 'image' && item.isToDisplay(albumID) ) {
2920 // 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>';
2921 // break;
2922 // }
2923 // }
2924
2925 //console.log(bgImage);
2926
2927 // new navigation bar items are not build in the DOM, but in memory
2928 G.GOM.navigationBar.$newContent=jQuery('<div class="nGY2Navigationbar"></div>');
2929 //G.GOM.navigationBar.$newContent = jQuery(bgImage );
2930 //console.log(G.GOM.navigationBar.$newContent);
2931
2932 //-- manage breadcrumb
2933 if( G.O.displayBreadcrumb == true && !G.O.thumbnailAlbumDisplayImage) {
2934 // retrieve new folder level
2935 var newLevel = 0,
2936 lstItems=[];
2937 if( albumIdx != 0 ) {
2938 var l=G.I.length,
2939 parentID=0;
2940
2941 lstItems.push(albumIdx);
2942 var curIdx=albumIdx;
2943 newLevel++;
2944
2945 while( G.I[curIdx].albumID != 0 && G.I[curIdx].albumID != -1) {
2946 for(var i=1; i < l; i++ ) {
2947 if( G.I[i].GetID() == G.I[curIdx].albumID ) {
2948 curIdx=i;
2949 lstItems.push(curIdx);
2950 newLevel++;
2951 break;
2952 }
2953 }
2954 }
2955 }
2956
2957 // build breadcrumb
2958 if( !(G.O.breadcrumbAutoHideTopLevel && newLevel == 0) ) {
2959 BreadcrumbBuild( lstItems );
2960 }
2961 }
2962
2963
2964 //-- manage and build tag filters
2965 if( G.galleryFilterTags.Get() != false ) {
2966 var nTags=G.I[albumIdx].albumTagList.length;
2967 if( nTags > 0 ) {
2968 for(var i=0; i < nTags; i++ ) {
2969 var s=G.I[albumIdx].albumTagList[i];
2970 var ic=G.O.icons.navigationFilterUnselected;
2971 var tagClass='Unselected';
2972 if( jQuery.inArray(s, G.I[albumIdx].albumTagListSel) >= 0 ) {
2973 tagClass='Selected';
2974 ic=G.O.icons.navigationFilterSelected;
2975 }
2976 var $newTag=jQuery('<div class="nGY2NavigationbarItem nGY2NavFilter'+tagClass+'">'+ic+' '+s+'</div>').appendTo(G.GOM.navigationBar.$newContent);
2977 $newTag.click(function() {
2978 var $this=jQuery(this);
2979 var tag=$this.text().replace(/^\s*|\s*$/, ''); //trim trailing/leading whitespace
2980 // if( $this.hasClass('oneTagUnselected') ){
2981 if( $this.hasClass('nGY2NavFilterUnselected') ){
2982 G.I[albumIdx].albumTagListSel.push(tag);
2983 }
2984 else {
2985 var tidx=jQuery.inArray(tag,G.I[albumIdx].albumTagListSel);
2986 if( tidx != -1 ) {
2987 G.I[albumIdx].albumTagListSel.splice(tidx,1);
2988 }
2989 }
2990 $this.toggleClass('nGY2NavFilters-oneTagUnselected nGY2NavFilters-oneTagSelected');
2991 DisplayAlbum('-1', G.I[albumIdx].GetID());
2992 });
2993 }
2994 var $newClearFilter=jQuery('<div class="nGY2NavigationbarItem nGY2NavFilterSelectAll">'+G.O.icons.navigationFilterSelectedAll+'</div>').appendTo(G.GOM.navigationBar.$newContent);
2995 $newClearFilter.click(function() {
2996 var nTags=G.I[albumIdx].albumTagList.length;
2997 G.I[albumIdx].albumTagListSel=[];
2998 for(var i=0; i <nTags; i++ ) {
2999 var s=G.I[albumIdx].albumTagList[i];
3000 G.I[albumIdx].albumTagListSel.push(s);
3001 }
3002 DisplayAlbum('-1', G.I[albumIdx].GetID());
3003 });
3004 }
3005 }
3006
3007 }
3008
3009 function BreadcrumbBuild(lstItems) {
3010
3011 // console.log(G.GOM.navigationBar.$newContent);
3012 jQuery('<div class="nGY2NavigationbarItem nGY2Breadcrumb"></div>').appendTo(G.GOM.navigationBar.$newContent);
3013 // console.log(G.GOM.navigationBar.$newContent);
3014
3015 if( G.O.breadcrumbOnlyCurrentLevel ) {
3016 // display only 1 separator and the current folder level
3017 if( lstItems.length == 0 ) {
3018 breadcrumbAdd(0);
3019 }
3020 else {
3021 var last=lstItems.length-1;
3022 if( lstItems.length == 1 ) {
3023 breadcrumbAddSeparator(0); // root level
3024 }
3025 else {
3026 breadcrumbAddSeparator(lstItems[0]);
3027 }
3028 breadcrumbAdd(lstItems[0]);
3029 }
3030 }
3031 else {
3032 // display the full breadcrum (full folder levels including root level)
3033 breadcrumbAdd(0);
3034 if( lstItems.length > 0 ) {
3035 breadcrumbAddSeparator(0);
3036 for(var i=lstItems.length-1; i>=0 ; i-- ) {
3037 breadcrumbAdd(lstItems[i]);
3038 if( i > 0 ) {
3039 breadcrumbAddSeparator(lstItems[i-1]);
3040 }
3041 }
3042 }
3043 }
3044
3045 }
3046
3047
3048 // Display gallery pagination
3049 function ManagePagination( albumIdx ) {
3050
3051 G.$E.conTnBottom.css('opacity', 0);
3052 G.$E.conTnBottom.children().remove();
3053
3054 if( G.GOM.items.length == 0 ) { return; } // no thumbnail to display
3055
3056 // calculate the number of pages
3057 var nbPages=Math.ceil((G.GOM.items[G.GOM.items.length - 1].row + 1)/G.galleryMaxRows.Get());
3058
3059 // only one page -> do not display pagination
3060 if( nbPages == 1 ) { return; }
3061
3062 // check if current page still exists (for example after a resize)
3063 if( G.GOM.pagination.currentPage > (nbPages-1) ) {
3064 G.GOM.pagination.currentPage = nbPages-1;
3065 }
3066
3067 GalleryRenderGetInterval();
3068 // nothing to display --> exit
3069 if( G.GOM.displayInterval.len == 0 ) { return; }
3070
3071 // display "previous"
3072 if( G.O.galleryPaginationMode == 'NUMBERS' && G.GOM.pagination.currentPage > 0 ) {
3073 var $eltPrev = jQuery('<div class="nGY2PaginationPrev">'+G.O.icons.paginationPrevious+'</div>').appendTo(G.$E.conTnBottom);
3074 $eltPrev.click(function(e) {
3075 paginationPreviousPage();
3076 });
3077 }
3078
3079 var firstPage = 0;
3080 var lastPage = nbPages;
3081 if( G.O.galleryPaginationMode != 'NUMBERS' ) {
3082 // no 'previous'/'next' and no max number of pagination items
3083 firstPage = 0;
3084 }
3085 else {
3086 // display pagination numbers and previous/next
3087 var vp = G.O.paginationVisiblePages;
3088 var numberOfPagesToDisplay = G.O.paginationVisiblePages;
3089 if( numberOfPagesToDisplay >= nbPages ) {
3090 firstPage = 0;
3091 }
3092 else {
3093 // we have more pages than we want to display
3094 var nbBeforeAfter = 0;
3095 if( isOdd(numberOfPagesToDisplay) ) {
3096 nbBeforeAfter = (numberOfPagesToDisplay + 1) / 2;
3097 }
3098 else {
3099 nbBeforeAfter = numberOfPagesToDisplay / 2;
3100 }
3101
3102 if( G.GOM.pagination.currentPage < nbBeforeAfter ) {
3103 firstPage = 0;
3104 lastPage = numberOfPagesToDisplay - 1;
3105 if( lastPage > nbPages ) {
3106 lastPage = nbPages - 1;
3107 }
3108 }
3109 else {
3110 firstPage = G.GOM.pagination.currentPage - nbBeforeAfter;
3111 lastPage = firstPage + numberOfPagesToDisplay;
3112 if( lastPage > nbPages ) {
3113 lastPage = nbPages - 1;
3114 }
3115 }
3116
3117 if( (lastPage - firstPage) < numberOfPagesToDisplay ) {
3118 firstPage = lastPage - numberOfPagesToDisplay;
3119 if( firstPage < 0 ) {
3120 firstPage = 0;
3121 }
3122 }
3123
3124 }
3125 }
3126
3127 // render pagination items
3128 for(var i = firstPage; i < lastPage; i++ ) {
3129 var c = '';
3130 var p = '';
3131
3132 switch( G.O.galleryPaginationMode ) {
3133 case 'NUMBERS':
3134 c = 'nGY2paginationItem';
3135 p = i + 1;
3136 break;
3137 case 'DOTS':
3138 c = 'nGY2paginationDot';
3139 break;
3140 case 'RECTANGLES':
3141 c = 'nGY2paginationRectangle';
3142 break;
3143 }
3144 if( i == G.GOM.pagination.currentPage ) {
3145 c += 'CurrentPage';
3146 }
3147
3148 var elt$ = jQuery('<div class="' + c + '">' + p + '</div>').appendTo(G.$E.conTnBottom);
3149 elt$.data('pageNumber', i );
3150 elt$.click( function(e) {
3151 G.GOM.pagination.currentPage = jQuery(this).data('pageNumber');
3152 TriggerCustomEvent('pageChanged');
3153
3154 // scroll to top of gallery if not displayed
3155 if( !inViewportVert(G.$E.base, 0) ) {
3156 $('html, body').animate({scrollTop: G.$E.base.offset().top}, 200);
3157 }
3158 GalleryDisplayPart1( true );
3159 GalleryDisplayPart2( true );
3160 });
3161
3162 }
3163
3164 // display "next"
3165 if( G.O.galleryPaginationMode == 'NUMBERS' && (G.GOM.pagination.currentPage + 1) < nbPages ) {
3166 var $eltNext = jQuery('<div class="nGY2PaginationNext">' + G.O.icons.paginationNext + '</div>').appendTo(G.$E.conTnBottom);
3167 $eltNext.click( function(e) {
3168 paginationNextPage();
3169 });
3170 }
3171
3172 G.$E.conTnBottom.css('opacity', 1);
3173
3174 }
3175 function isOdd(num) { return (num % 2) == 1;}
3176
3177 // pagination - next page
3178 function paginationNextPage() {
3179 var aIdx = G.GOM.albumIdx,
3180 n1 = 0;
3181 ThumbnailHoverOutAll();
3182
3183 // pagination - max lines per page mode
3184 if( G.galleryMaxRows.Get() > 0 ) {
3185 // number of pages
3186 n1 = (G.GOM.items[G.GOM.items.length - 1].row + 1) / G.galleryMaxRows.Get();
3187 }
3188 var n2 = Math.ceil(n1);
3189 var pn = G.GOM.pagination.currentPage;
3190 if( pn < (n2-1) ) {
3191 pn++;
3192 }
3193 else {
3194 pn = 0;
3195 }
3196
3197 G.GOM.pagination.currentPage = pn;
3198 TriggerCustomEvent('pageChanged');
3199
3200 // scroll to top of gallery if not displayed
3201 if( !inViewportVert(G.$E.base, 0) ) {
3202 $('html, body').animate({scrollTop: G.$E.base.offset().top}, 200);
3203 }
3204 GalleryDisplayPart1( true );
3205 GalleryDisplayPart2( true );
3206 }
3207
3208 // pagination - previous page
3209 function paginationPreviousPage() {
3210 // var aIdx=G.$E.conTnBottom.data('galleryIdx'),
3211 var aIdx = G.GOM.albumIdx,
3212 n1 = 0;
3213
3214 ThumbnailHoverOutAll();
3215
3216 // pagination - max lines per page mode
3217 if( G.galleryMaxRows.Get() > 0 ) {
3218 // number of pages
3219 n1 = (G.GOM.items[G.GOM.items.length - 1].row + 1) / G.galleryMaxRows.Get();
3220 }
3221 var n2 = Math.ceil(n1);
3222
3223 // var pn=G.$E.conTnBottom.data('currentPageNumber');
3224 var pn = G.GOM.pagination.currentPage;
3225 if( pn > 0 ) {
3226 pn--;
3227 }
3228 else {
3229 pn = n2 - 1;
3230 }
3231
3232 G.GOM.pagination.currentPage = pn;
3233 TriggerCustomEvent('pageChanged');
3234
3235 // scroll to top of gallery if not displayed
3236 if( !inViewportVert(G.$E.base, 0) ) {
3237 $('html, body').animate({scrollTop: G.$E.base.offset().top}, 200);
3238 }
3239 GalleryDisplayPart1( true );
3240 GalleryDisplayPart2( true );
3241 }
3242
3243 // retrieve the from/to intervall for gallery thumbnail render
3244 function GalleryRenderGetInterval() {
3245 G.GOM.displayInterval.from = 0;
3246 G.GOM.displayInterval.len = G.I.length;
3247
3248 switch( G.galleryDisplayMode.Get() ) {
3249 case 'PAGINATION':
3250 if( G.layout.support.rows ) {
3251 var nbTn = G.GOM.items.length;
3252 var firstRow = G.GOM.pagination.currentPage * G.galleryMaxRows.Get();
3253 var lastRow = firstRow + G.galleryMaxRows.Get();
3254 var firstTn = -1;
3255 G.GOM.displayInterval.len = 0;
3256 for( var i = 0; i < nbTn ; i++ ) {
3257 var curTn = G.GOM.items[i];
3258 if( curTn.row >= firstRow && curTn.row < lastRow ) {
3259 if( firstTn == -1 ) {
3260 G.GOM.displayInterval.from = i;
3261 firstTn = i;
3262 }
3263 G.GOM.displayInterval.len++;
3264 }
3265 }
3266 }
3267 break;
3268 case 'MOREBUTTON':
3269 if( G.layout.support.rows ) {
3270 var nbTn = G.GOM.items.length;
3271 var lastRow = G.O.galleryDisplayMoreStep * (G.GOM.displayedMoreSteps+1);
3272 G.GOM.displayInterval.len = 0;
3273 for( var i = 0; i < nbTn ; i++ ) {
3274 var curTn = G.GOM.items[i];
3275 if( curTn.row < lastRow ) {
3276 G.GOM.displayInterval.len++;
3277 }
3278 }
3279 }
3280 break;
3281 case 'ROWS':
3282 if( G.layout.support.rows ) {
3283 var nbTn = G.GOM.items.length;
3284 var lastRow = G.galleryMaxRows.Get();
3285 if( G.galleryLastRowFull.Get() && G.GOM.lastFullRow != -1 ) {
3286 if( lastRow > (G.GOM.lastFullRow + 1) ) {
3287 lastRow = G.GOM.lastFullRow + 1;
3288 }
3289 }
3290 G.GOM.displayInterval.len = 0;
3291 for( var i = 0; i < nbTn ; i++ ) {
3292 var curTn = G.GOM.items[i];
3293 if( curTn.row < lastRow ) {
3294 G.GOM.displayInterval.len++;
3295 }
3296 }
3297 }
3298 break;
3299 default:
3300 case 'FULLCONTENT':
3301 if( G.layout.support.rows && G.galleryLastRowFull.Get() && G.GOM.lastFullRow != -1 ) {
3302 var nbTn = G.GOM.items.length;
3303 var lastRow = G.GOM.lastFullRow + 1;
3304 G.GOM.displayInterval.len = 0;
3305 for( var i = 0; i < nbTn ; i++ ) {
3306 var curTn = G.GOM.items[i];
3307 if( curTn.row < lastRow ) {
3308 G.GOM.displayInterval.len++;
3309 }
3310 }
3311 }
3312 break;
3313 }
3314 }
3315
3316
3317 // RENDER THE GALLERY
3318 function GalleryRender( albumIdx ) {
3319 TriggerCustomEvent('galleryRenderStart');
3320
3321 clearTimeout(G.GOM.slider.timerID);
3322 G.GOM.slider.hostIdx = -1; // disabled slider on thumbnail
3323
3324 var fu=G.O.fnGalleryRenderStart;
3325 if( fu !== null ) {
3326 typeof fu == 'function' ? fu(albumIdx) : window[fu](albumIdx);
3327 }
3328
3329 G.layout.SetEngine();
3330 G.galleryResizeEventEnabled = false;
3331 G.GOM.albumIdx = -1;
3332 G.GOM.lastDisplayedIdx = -1;
3333
3334 // pagination
3335 if( G.$E.conTnBottom !== undefined ) {
3336 // G.$E.conTnBottom.children().remove();
3337 G.$E.conTnBottom.empty();
3338 }
3339
3340 // navigation toolbar (breadcrumb + tag filters)
3341 GalleryNavigationBar(albumIdx);
3342
3343 if( G.GOM.firstDisplay ) {
3344 G.GOM.firstDisplay = false;
3345 var d = Date.now()-G.GOM.firstDisplayTime;
3346 if( d < G.O.galleryRenderDelay ) {
3347 // setTimeout( function() { GalleryRenderPart1( albumIdx )}, G.O.galleryRenderDelay-d );
3348 requestTimeout( function() { GalleryRenderPart1( albumIdx )}, G.O.galleryRenderDelay-d );
3349 }
3350 else {
3351 GalleryRenderPart1( albumIdx );
3352 }
3353 G.O.galleryRenderDelay = 0;
3354
3355 }
3356 else {
3357 var hideNavigationBar = false;
3358 if( G.GOM.navigationBar.$newContent.children().length == 0 ) {
3359 hideNavigationBar = true;
3360 }
3361
3362 // hide everything
3363 var tweenable = new NGTweenable();
3364 tweenable.tween({
3365 from: { 'opacity': 1 },
3366 to: { 'opacity': 0 },
3367 duration: 300,
3368 easing: 'easeInQuart',
3369 attachment: { h: hideNavigationBar },
3370 step: function (state, att) {
3371 // window.ng_draf( function() {
3372 G.$E.conTnParent.css({'opacity': state.opacity });
3373 if( att.h ) {
3374 G.$E.conNavigationBar.css({ 'opacity': state.opacity });
3375 }
3376 // });
3377 },
3378 finish: function (state, att) {
3379 // window.ng_draf( function() {
3380 if( att.h ) {
3381 G.$E.conNavigationBar.css({ 'opacity': 0, 'display': 'none' });
3382 }
3383 // scroll to top of the gallery if needed
3384 var galleryOTop = G.$E.base.offset().top;
3385 if( galleryOTop < G.GOM.cache.viewport.t ) {
3386 // jQuery('html, body').animate({scrollTop: galleryOTop}, 200);
3387 jQuery('html, body').animate({scrollTop: galleryOTop}, 500, "linear", function() {
3388 GalleryRenderPart1( albumIdx );
3389 });
3390 }
3391 else {
3392 GalleryRenderPart1( albumIdx );
3393 }
3394 // });
3395 }
3396 });
3397 }
3398 }
3399
3400
3401 function GalleryRenderPart1( albumIdx ) {
3402 // display new navigation bar
3403 var oldN = G.$E.conNavigationBar.children().length;
3404 G.$E.conNavigationBar.empty();
3405 G.GOM.navigationBar.$newContent.children().clone(true,true).appendTo(G.$E.conNavigationBar);
3406 // G.GOM.navigationBar.$newContent.appendTo(G.$E.conNavigationBar);
3407 if( G.$E.conNavigationBar.children().length > 0 && oldN == 0 ) {
3408 G.$E.conNavigationBar.css({ 'opacity': 0, 'display': 'block' });
3409 var tweenable = new NGTweenable();
3410 tweenable.tween({
3411 from: { opacity: 0 },
3412 to: { opacity: 1 },
3413 duration: 200,
3414 easing: 'easeInQuart',
3415 step: function (state) {
3416 // window.ng_draf( function() {
3417 G.$E.conNavigationBar.css( state );
3418 // });
3419 },
3420 finish: function (state) {
3421 // window.ng_draf( function() {
3422 G.$E.conNavigationBar.css({ 'opacity': 1 });
3423 // display gallery
3424 // GalleryRenderPart2( albumIdx );
3425 // setTimeout(function(){ GalleryRenderPart2(albumIdx) }, 60);
3426 requestTimeout(function(){ GalleryRenderPart2(albumIdx) }, 60);
3427 // });
3428 }
3429 });
3430 }
3431 else {
3432 // display gallery
3433 // GalleryRenderPart2( albumIdx );
3434 // setTimeout(function(){ GalleryRenderPart2(albumIdx) }, 60);
3435 requestTimeout(function(){ GalleryRenderPart2(albumIdx) }, 60);
3436 }
3437
3438 }
3439
3440 // Gallery render part 2 -> remove all thumbnails
3441 function GalleryRenderPart2(albumIdx) {
3442 G.GOM.lastZIndex = parseInt(G.$E.base.css('z-index'));
3443 if( isNaN(G.GOM.lastZIndex) ) {
3444 G.GOM.lastZIndex=0;
3445 }
3446 G.$E.conTnParent.css({ 'opacity': 0 });
3447 G.$E.conTn.off().empty();
3448 var l = G.I.length;
3449 for( var i = 0; i < l ; i++ ) {
3450 // reset each item
3451 var item = G.I[i];
3452 item.hovered = false;
3453 item.$elt = null;
3454 item.$Elts = [];
3455 item.eltTransform = [];
3456 item.eltFilter = [];
3457 item.width = 0;
3458 item.height = 0;
3459 item.left = 0;
3460 item.top = 0;
3461 item.resizedContentWidth = 0;
3462 item.resizedContentHeight = 0;
3463 item.thumbnailImgRevealed = false;
3464 }
3465
3466 if( G.CSStransformName == null ) {
3467 G.$E.conTn.css('left', '0px');
3468 }
3469 else {
3470 // G.$E.conTn.css( G.CSStransformName, 'translateX(0px)');
3471 G.$E.conTn.css( G.CSStransformName, 'none');
3472 }
3473
3474 // setTimeout(function(){ GalleryRenderPart3(albumIdx) }, 60);
3475 requestTimeout(function(){ GalleryRenderPart3(albumIdx) }, 60);
3476 // GalleryRenderPart3(albumIdx);
3477
3478 }
3479
3480 // Gallery render part 2 -> start building the new gallery
3481 function GalleryRenderPart3(albumIdx) {
3482 var d = new Date();
3483
3484 G.$E.conTnParent.css( 'opacity', 1);
3485
3486 G.GOM.items = [];
3487 G.GOM.displayedMoreSteps = 0;
3488 // retrieve label height
3489 if( G.O.thumbnailLabel.get('position') == 'onBottom' ) {
3490 // retrieve height each time because size can change depending on thumbnail's settings
3491 G.tn.labelHeight[G.GOM.curNavLevel]=ThumbnailGetLabelHeight();
3492 }
3493 else {
3494 G.tn.labelHeight[G.GOM.curNavLevel]=0;
3495 }
3496 G.GOM.albumIdx=albumIdx;
3497
3498 TriggerCustomEvent('galleryRenderEnd');
3499 var fu=G.O.fnGalleryRenderEnd;
3500 if( fu !== null ) {
3501 typeof fu == 'function' ? fu(albumIdx) : window[fu](albumIdx);
3502 }
3503
3504 // Step 1: populate GOM
3505 if( GalleryPopulateGOM() ) {
3506
3507 // step 2: calculate layout
3508 GallerySetLayout();
3509
3510 // step 3: display gallery
3511 GalleryAppear();
3512 // GalleryDisplay( false );
3513 GalleryDisplayPart1( false );
3514 // setTimeout(function(){ GalleryDisplayPart2( false ) }, 120);
3515 requestTimeout(function(){ GalleryDisplayPart2( false ) }, 120);
3516 }
3517 else {
3518 G.galleryResizeEventEnabled = true;
3519 }
3520
3521 if( G.O.debugMode ) { console.log('GalleryRenderPart3: '+ (new Date()-d)); }
3522
3523 }
3524
3525
3526 // Resize the gallery
3527 function GalleryResize() {
3528 var d = new Date();
3529 G.galleryResizeEventEnabled = false;
3530 // G.GOM.cache.areaWidth=G.$E.conTnParent.width();
3531 if( GallerySetLayout() == false ) {
3532 G.galleryResizeEventEnabled = true;
3533 if( G.O.debugMode ) { console.log('GalleryResize1: '+ (new Date()-d)); }
3534 return;
3535 }
3536 if( G.O.debugMode ) { console.log('GalleryResizeSetLayout: '+ (new Date()-d)); }
3537
3538 GalleryDisplayPart1( false );
3539 GalleryDisplayPart2( false );
3540
3541 if( G.O.debugMode ) { console.log('GalleryResizeFull: '+ (new Date()-d)); }
3542 }
3543
3544
3545
3546 // copy items (album content) to GOM
3547 function GalleryPopulateGOM() {
3548
3549 var preloadImages = '';
3550 var imageSizeRequested = false;
3551 var albumID = G.I[G.GOM.albumIdx].GetID();
3552 var l = G.I.length;
3553 var cnt = 0;
3554
3555 for( var idx = 0; idx < l; idx++ ) {
3556 var item = G.I[idx];
3557 // check album
3558 if( item.isToDisplay(albumID) ) {
3559 var w = item.thumbImg().width;
3560 var h = item.thumbImg().height;
3561 // if unknown image size and layout is not grid --> we need to retrieve the size of the images
3562 if( G.layout.prerequisite.imageSize && ( w == 0 || h == 0) ) {
3563 // if( true ) {
3564 imageSizeRequested = true;
3565 preloadImages += '<img src="'+item.thumbImg().src+'" data-idx="'+cnt+'" data-albumidx="'+G.GOM.albumIdx+'">';
3566 }
3567
3568 // set default size if required
3569 if( h == 0 ) {
3570 h = G.tn.defaultSize.getHeight();
3571 }
3572 if( w == 0 ) {
3573 w = G.tn.defaultSize.getWidth();
3574 }
3575 var tn = new G.GOM.GTn(idx, w, h);
3576 G.GOM.items.push(tn);
3577 cnt++;
3578 }
3579 }
3580
3581 TriggerCustomEvent('galleryObjectModelBuilt');
3582 var fu = G.O.fnGalleryObjectModelBuilt;
3583 if( fu !== null ) {
3584 typeof fu == 'function' ? fu() : window[fu]();
3585 }
3586
3587 if( imageSizeRequested ) {
3588 // preload images to retrieve their size and then resize the gallery (=GallerySetLayout()+ GalleryDisplay())
3589 var $newImg = jQuery(preloadImages);
3590 var gi_imgLoad = ngimagesLoaded( $newImg );
3591 $newImg = null;
3592 gi_imgLoad.on( 'progress', function( instance, image ) {
3593
3594 if( image.isLoaded ) {
3595 var idx = image.img.getAttribute('data-idx');
3596 var albumIdx = image.img.getAttribute('data-albumidx');
3597 if( albumIdx == G.GOM.albumIdx ) {
3598 // ignore event if not on current album
3599 var curTn = G.GOM.items[idx];
3600 curTn.imageWidth = image.img.naturalWidth;
3601 curTn.imageHeight = image.img.naturalHeight;
3602 var item = G.I[curTn.thumbnailIdx];
3603 item.thumbs.width[G.GOM.curNavLevel][G.GOM.curWidth] = curTn.imageWidth;
3604 item.thumbs.height[G.GOM.curNavLevel][G.GOM.curWidth] = curTn.imageHeight;
3605
3606 // resize the gallery
3607 G.GalleryResizeThrottled();
3608
3609 // set the retrieved size to all levels with same configuration
3610 var object = item.thumbs.width.l1;
3611 for (var property in object) {
3612 if (object.hasOwnProperty(property)) {
3613 if( property != G.GOM.curWidth ) {
3614 if( G.tn.settings.width.l1[property] == G.tn.settings.getW() && G.tn.settings.height.l1[property] == G.tn.settings.getH() ) {
3615 item.thumbs.width.l1[property] = curTn.imageWidth;
3616 item.thumbs.height.l1[property] = curTn.imageHeight;
3617 }
3618 }
3619 }
3620 }
3621 object = item.thumbs.width.lN;
3622 for (var property in object) {
3623 if (object.hasOwnProperty(property)) {
3624 if( property != G.GOM.curWidth ) {
3625 if( G.tn.settings.width.lN[property] == G.tn.settings.getW() && G.tn.settings.height.lN[property] == G.tn.settings.getH() ) {
3626 item.thumbs.width.lN[property] = curTn.imageWidth;
3627 item.thumbs.height.lN[property] = curTn.imageHeight;
3628 }
3629 }
3630 }
3631 }
3632 }
3633 }
3634 });
3635 G.galleryResizeEventEnabled = true;
3636 return false;
3637 }
3638 else {
3639 return true;
3640 }
3641
3642 }
3643
3644 //----- Calculate the layout of the thumbnails
3645 function GallerySetLayout() {
3646 var r = true;
3647 // width of the available area
3648 G.GOM.cache.areaWidth = G.$E.conTnParent.width();
3649 G.GOM.displayArea = { width:0, height:0 };
3650
3651 switch( G.layout.engine ) {
3652 case 'JUSTIFIED':
3653 r = GallerySetLayoutWidthtAuto();
3654 break;
3655 case 'CASCADING':
3656 r = GallerySetLayoutHeightAuto();
3657 break;
3658 case 'MOSAIC':
3659 r = GallerySetLayoutMosaic();
3660 break;
3661 case 'GRID':
3662 default:
3663 r = GallerySetLayoutGrid();
3664 break;
3665 }
3666
3667 TriggerCustomEvent('galleryLayoutApplied');
3668 var fu = G.O.fnGalleryLayoutApplied;
3669 if( fu !== null ) {
3670 typeof fu == 'function' ? fu() : window[fu]();
3671 }
3672 return r;
3673
3674 }
3675
3676
3677 //----- CASCADING LAYOUT
3678 function GallerySetLayoutHeightAuto() {
3679 var curCol = 0,
3680 areaWidth = G.GOM.cache.areaWidth,
3681 curRow = 0,
3682 colHeight = [],
3683 maxCol = NbThumbnailsPerRow(areaWidth),
3684 gutterWidth = 0,
3685 gutterHeight = G.tn.opt.Get('gutterHeight');
3686 var w = 0;
3687 var scaleFactor = 1;
3688 var tnWidth = G.tn.defaultSize.getOuterWidth();
3689 var nbTn = G.GOM.items.length;
3690 var curPosY = 0;
3691
3692 if( G.O.thumbnailAlignment == 'justified' ) {
3693 maxCol = Math.min(maxCol, nbTn);
3694 gutterWidth = ( maxCol == 1 ? 0 : (areaWidth - (maxCol * tnWidth) ) / (maxCol - 1) );
3695 }
3696 else {
3697 gutterWidth=G.tn.opt.Get('gutterWidth');
3698 }
3699
3700
3701 var borderWidth = G.tn.borderWidth * 2;
3702 var borderHeight = G.tn.borderHeight * 2;
3703
3704 G.GOM.lastFullRow=-1; // feature disabled
3705
3706 // Retrieve the real used width of the area (the evaluation is based on the content of the first line)
3707 if( G.O.thumbnailAlignment == 'fillWidth' ) {
3708 // fillWidth --> evaluate scale factor and number of columns
3709 var totalGutterWidth = (maxCol - 1) * gutterWidth;
3710 scaleFactor = (areaWidth - totalGutterWidth) / (maxCol * tnWidth);
3711 if( scaleFactor > 1 ) {
3712 maxCol++; // add one column and re-evaluate the scale factor
3713 }
3714 totalGutterWidth = (maxCol - 1) * gutterWidth;
3715 scaleFactor = Math.min( (areaWidth - totalGutterWidth) / (maxCol*tnWidth), 1); // no upscale
3716 }
3717
3718
3719 tnWidth = tnWidth * scaleFactor;
3720 var contentWidth = tnWidth - borderWidth;
3721
3722 // loop to position the thumbnails, and set their size
3723 var baseHeight = G.tn.opt.Get('baseGridHeight') * scaleFactor;
3724 for( var i = 0; i < nbTn ; i++ ) {
3725 var curTn = G.GOM.items[i];
3726 if( curTn.deleted == true ) { break; } // item is logically deleted
3727 if( curTn.imageHeight > 0 && curTn.imageWidth > 0 ) {
3728 var curPosX = 0,
3729 curPosY = 0;
3730 var imageRatio = curTn.imageHeight / curTn.imageWidth;
3731 // curTn.resizedContentWidth = tnWidth - borderWidth;
3732 curTn.resizedContentWidth = contentWidth;
3733 curTn.resizedContentHeight = curTn.resizedContentWidth * imageRatio;
3734 if( baseHeight > 0 ) {
3735 // grid based vertical position
3736 var t= Math.max( Math.trunc(curTn.resizedContentHeight/baseHeight), 1) ;
3737 curTn.resizedContentHeight = baseHeight * t + ((t-1)*(borderHeight+gutterHeight));
3738 }
3739
3740 curTn.height = curTn.resizedContentHeight + borderHeight + G.tn.labelHeight.get();
3741 curTn.width = tnWidth;
3742 curTn.row = 0;
3743
3744 if( curRow == 0 ) {
3745 // first row
3746 curPosX = curCol * (tnWidth + gutterWidth);
3747 colHeight[curCol] = curTn.height + gutterHeight;
3748
3749 curCol++;
3750 if( curCol >= maxCol ) {
3751 curCol = 0;
3752 curRow++;
3753 }
3754 }
3755 else {
3756 var c=0,
3757 minColHeight=colHeight[0];
3758 for( var j = 1; j < maxCol; j++) {
3759 if( (colHeight[j] + 5) < minColHeight ) { // +5 --> threshold
3760 minColHeight = colHeight[j];
3761 c = j;
3762 //break;
3763 }
3764 }
3765 curPosY = colHeight[c];
3766 curPosX = c * (tnWidth + gutterWidth);
3767 colHeight[c] = curPosY + curTn.height + gutterHeight;
3768 }
3769
3770 var x = curPosX;
3771 if( G.O.RTL) {
3772 x= w - curPosX - tnWidth;
3773 }
3774
3775 curTn.left = x;
3776 curTn.top = curPosY;
3777 }
3778 }
3779
3780 G.GOM.displayArea.width= maxCol * (tnWidth + gutterWidth) - gutterWidth;
3781 return true;
3782 }
3783
3784
3785 //----- JUSTIFIED LAYOUT
3786 function GallerySetLayoutWidthtAuto() {
3787 var curWidth = 0,
3788 areaWidth = G.GOM.cache.areaWidth,
3789 lastPosX = 0,
3790 curPosY = 0,
3791 rowLastItem = [],
3792 rowNum = 0,
3793 rowHeight = [],
3794 bNewRow = false,
3795 cnt = 0,
3796 gutterWidth = G.tn.opt.Get('gutterWidth'),
3797 gutterHeight = G.tn.opt.Get('gutterHeight');
3798 // by grief-of-these-days
3799 var maxRowHeightVertical = 0; // max height of a row with vertical thumbs
3800 var maxRowHeightHorizontal = 0; // max height of a row with horizontal thumbs
3801 var rowHasVertical = false; // current row has vertical thumbs
3802 var rowHasHorizontal = false; // current row has horizontal thumbs
3803
3804 var tnHeight = G.tn.defaultSize.getOuterHeight();
3805 var borderWidth = G.tn.borderWidth * 2;
3806 var borderHeight = G.tn.borderHeight * 2;
3807 var nbTnInCurrRow = 1;
3808 var nbTn = G.GOM.items.length;
3809
3810 // first loop --> retrieve each row image height
3811 for( var i = 0; i < nbTn ; i++ ) {
3812 var curTn = G.GOM.items[i];
3813 if( curTn.deleted == true ) { break; } // item is logically deleted
3814 if( curTn.imageWidth > 0 ) {
3815 var imageRatio = curTn.imageWidth / curTn.imageHeight;
3816 var imageWidth = Math.floor( tnHeight * imageRatio );
3817
3818 if( bNewRow ) {
3819 bNewRow = false;
3820 rowNum++;
3821 curWidth = 0;
3822 rowHasVertical = false;
3823 rowHasHorizontal = false;
3824 nbTnInCurrRow = 1;
3825 }
3826 // by grief-of-these-days
3827 if( curTn.imageHeight > curTn.imageWidth ) {
3828 rowHasVertical = true;
3829 }
3830 else {
3831 rowHasHorizontal = true;
3832 }
3833
3834 if( (curWidth + gutterWidth + imageWidth) < (areaWidth - (nbTnInCurrRow * borderWidth)) ) {
3835 // enough place left in the current row
3836 curWidth += imageWidth + gutterWidth;
3837 rowHeight[rowNum] = tnHeight;
3838
3839 // prevent incomplete row from being heigher than the previous ones.
3840 // by grief-of-these-days
3841 var rowHeightLimit = Math.max(rowHasVertical ? maxRowHeightVertical : 0, rowHasHorizontal ? maxRowHeightHorizontal : 0);
3842 if( rowHeightLimit > 0 ) {
3843 rowHeight[rowNum] = Math.min(rowHeight[rowNum], rowHeightLimit);
3844 }
3845
3846 rowLastItem[rowNum] = i;
3847 }
3848 else {
3849 // new row after current item --> we need to adujet the row height to have enough space for the current thumbnail
3850 curWidth += gutterWidth+imageWidth;
3851 var ratio = (areaWidth - nbTnInCurrRow * borderWidth) / curWidth;
3852 var rH = Math.floor(tnHeight * ratio);
3853 rowHeight[rowNum] = rH;
3854
3855 // save the max row height for each thumb orientation.
3856 // by grief-of-these-days
3857 if( rowHasVertical ) {
3858 maxRowHeightVertical = Math.max( maxRowHeightVertical, rH );
3859 }
3860 if( rowHasHorizontal ) {
3861 maxRowHeightHorizontal = Math.max( maxRowHeightHorizontal, rH );
3862 }
3863
3864 rowLastItem[rowNum] = i;
3865 bNewRow = true;
3866 }
3867 cnt++;
3868 nbTnInCurrRow++;
3869 }
3870 }
3871
3872 rowNum = 0;
3873 curPosY = 0;
3874 lastPosX = 0;
3875 cnt = 0;
3876
3877 G.GOM.lastFullRow = 0; // display at leat 1 row (even if not full)
3878
3879 // second loop --> calculate each thumbnail size
3880 for( var i = 0; i < nbTn ; i++ ) {
3881 var curTn = G.GOM.items[i];
3882 if( curTn.imageWidth > 0 ) {
3883 var imageRatio = curTn.imageWidth / curTn.imageHeight;
3884 var imageWidth = Math.floor( imageRatio * rowHeight[rowNum] ); // border is already NOT included
3885
3886 if( i == rowLastItem[rowNum] ) {
3887 // row last item --> adjust image width because of rounding problems
3888 if( rowLastItem.length != (rowNum+1) ) {
3889 // last item in current row -> use the full remaining width
3890 imageWidth = areaWidth - lastPosX - borderWidth;
3891 }
3892 else {
3893 // very last item (on the last row)
3894 if( (lastPosX + gutterWidth + imageWidth + borderWidth ) > areaWidth ) {
3895 // reduce size if image is wider as the remaining space
3896 imageWidth = areaWidth - lastPosX - borderWidth;
3897 }
3898 }
3899 }
3900
3901 var rh = parseInt( rowHeight[rowNum] );
3902 imageWidth = parseInt( imageWidth );
3903
3904 // thumbnail image size
3905 curTn.resizedContentWidth = imageWidth;
3906 curTn.resizedContentHeight = rh;
3907 // thumbnail position and size
3908 curTn.width = imageWidth + borderWidth;
3909 curTn.height= rh + G.tn.labelHeight.get() + borderHeight;
3910 curTn.row = rowNum;
3911
3912 curTn.top = curPosY;
3913 var x = lastPosX;
3914 if( G.O.RTL) {
3915 x = areaWidth - lastPosX - curTn.width ;
3916 }
3917 curTn.left = x;
3918
3919 lastPosX += curTn.width + gutterWidth;
3920
3921 if( i == rowLastItem[rowNum] ) {
3922 // start a new row
3923 curPosY += curTn.height + gutterHeight;
3924 G.GOM.lastFullRow = rowNum - 1;
3925 rowNum++;
3926 lastPosX = 0;
3927 }
3928 cnt++;
3929 }
3930 else {
3931 return false;
3932 }
3933 }
3934
3935 if( false ) {
3936 var newTop = 0;
3937 if( typeof GOMidx !== 'undefined' ) {
3938 // hover effect on gallery (vs on thumbnail) --> experimental / not used
3939 if( G.GOM.albumIdx != -1 ) {
3940 var hoveredTn = G.GOM.items[GOMidx];
3941 var item = G.I[hoveredTn.thumbnailIdx];
3942
3943 // hovered thumbnail
3944 hoveredTn.width += 40;
3945 hoveredTn.height += 40;
3946 // todo : left
3947
3948 for( var i = 0; i < nbTn ; i++ ) {
3949 var curTn = G.GOM.items[i];
3950 if( curTn.imageWidth > 0 ) {
3951 if( curTn.row == hoveredTn.row ) {
3952 // hovered row
3953 newTop = 40;
3954 if( hoveredTn.thumbnailIdx != curTn.thumbnailIdx ) {
3955 // not hovered thumbnail
3956 // curTn.resizedContentWidth+=10;
3957 // curTn.resizedContentHeight+=20;
3958 // curTn.width+=10;
3959 curTn.top += 30;
3960 curTn.width -= 20;
3961 curTn.height -= 20;
3962 }
3963 }
3964 else {
3965 // not hovered row
3966 if( curTn.row == 0 ) {
3967 // first row
3968 }
3969 else {
3970 curTn.top += newTop;
3971 }
3972 }
3973 }
3974 }
3975 }
3976 }
3977 }
3978
3979 G.GOM.displayArea.width = areaWidth;
3980 return true;
3981 }
3982
3983
3984 //----- MOSAIC LAYOUT
3985 // Grid using a user defined pattern layout
3986 // With this layout, a pattern definition is handeld a row
3987 function GallerySetLayoutMosaic() {
3988 var areaWidth = G.GOM.cache.areaWidth;
3989 var gutterHeight = G.tn.opt.Get('gutterHeight');
3990 var gutterWidth = G.tn.opt.Get('gutterWidth');
3991 var borderWidth = G.tn.borderWidth * 2;
3992 var borderHeight = G.tn.borderHeight * 2;
3993
3994 var nbTn = G.GOM.items.length;
3995 var row = 0;
3996 var h = 0;
3997 var n = 0;
3998
3999
4000 // first loop: evaluate the gallery width based on the first row
4001 var nbCols = 0;
4002 var maxW = 0;
4003 var mosaicPattern = G.tn.settings.getMosaic();
4004 for( var i = 0; i < nbTn ; i++ ) {
4005 var curPatternElt = mosaicPattern[n];
4006
4007 var cLeft = (curPatternElt.c - 1) * G.tn.defaultSize.getOuterWidth() + (curPatternElt.c - 1) * gutterWidth;
4008 var cWidth = curPatternElt.w * G.tn.defaultSize.getOuterWidth() + (curPatternElt.w - 1) * gutterWidth;
4009
4010 maxW=Math.max(maxW, cLeft + cWidth );
4011
4012 nbCols=Math.max(nbCols, (curPatternElt.c - 1) + curPatternElt.w );
4013
4014 n++;
4015 if( n >= mosaicPattern.length ) {
4016 // end of pattern
4017 break;
4018 }
4019 }
4020 var totalGutterWidth = (nbCols - 1) * gutterWidth;
4021 var scaleFactor = Math.min( (areaWidth - totalGutterWidth ) / ( maxW - totalGutterWidth ), 1);
4022
4023 // second loop: position all the thumbnails based on the layout pattern
4024 row = 0;
4025 n = 0;
4026 var mosaicPattern = G.tn.settings.getMosaic();
4027 for( var i = 0; i < nbTn ; i++ ) {
4028 var curTn = G.GOM.items[i];
4029 var curPatternElt = mosaicPattern[n];
4030
4031 curTn.top = (curPatternElt.r - 1) * G.tn.defaultSize.getOuterHeight()*scaleFactor + (curPatternElt.r - 1) * gutterHeight + row * h + (G.tn.labelHeight.get()*(curPatternElt.r-1)) ;
4032 if( row > 0 ) {
4033 curTn.top += gutterHeight;
4034 }
4035
4036 curTn.left = (curPatternElt.c - 1) * G.tn.defaultSize.getOuterWidth()*scaleFactor + (curPatternElt.c - 1) * gutterWidth;
4037
4038 curTn.height = curPatternElt.h * G.tn.defaultSize.getOuterHeight() * scaleFactor + (curPatternElt.h - 1) * gutterHeight + (G.tn.labelHeight.get() * curPatternElt.h);
4039 curTn.resizedContentHeight = curTn.height - G.tn.labelHeight.get() - borderHeight;
4040
4041 curTn.width = curPatternElt.w * G.tn.defaultSize.getOuterWidth()*scaleFactor + (curPatternElt.w - 1) * gutterWidth;
4042 curTn.resizedContentWidth = curTn.width - borderWidth ;
4043
4044 curTn.row = row;
4045 if( row == 0 ) {
4046 h=Math.max(h, curTn.top + curTn.height);
4047 }
4048
4049 n++;
4050 if( n >= mosaicPattern.length ) {
4051 // end pattern -> new line
4052 n = 0;
4053 row++;
4054 }
4055 }
4056
4057 G.GOM.displayArea.width = (maxW - totalGutterWidth) * scaleFactor + totalGutterWidth;
4058 return true;
4059 }
4060
4061
4062
4063 // --- GRID LAYOUT
4064 function GallerySetLayoutGrid() {
4065 var curPosX= 0,
4066 curPosY= 0,
4067 areaWidth= G.GOM.cache.areaWidth,
4068 gutterWidth= 0,
4069 gutterHeight= G.tn.opt.Get('gutterHeight'),
4070 maxCol= NbThumbnailsPerRow(areaWidth),
4071 w= 0,
4072 cols= [],
4073 curCol= 0,
4074 newAreaWidth = areaWidth,
4075 tnWidth= G.tn.defaultSize.getOuterWidth();
4076 var scaleFactor = 1;
4077 var nbTn= G.GOM.items.length;
4078 var borderWidth = G.tn.borderWidth * 2;
4079 var borderHeight =G.tn.borderHeight * 2;
4080
4081 // retrieve gutter width
4082 if( G.O.thumbnailAlignment == 'justified' ) {
4083 maxCol = Math.min( maxCol, nbTn);
4084 gutterWidth = (maxCol==1 ? 0 : (areaWidth-(maxCol*tnWidth))/(maxCol-1));
4085 }
4086 else {
4087 gutterWidth = G.tn.opt.Get('gutterWidth');
4088 }
4089
4090 // first loop to retrieve the real used width of the area (the evaluation is based on the content of the first line)
4091 // Retrieve the real used width of the area (the evaluation is based on the content of the first line)
4092 if( G.O.RTL || G.O.thumbnailAlignment == 'fillWidth' ) {
4093 // scaled --> evaluate scale factor and number of columns
4094 var totalGutterWidth = (maxCol-1) * gutterWidth;
4095 scaleFactor = (areaWidth - totalGutterWidth) / (maxCol*tnWidth);
4096 if( scaleFactor > 1 ) {
4097 maxCol++; // add one column and re-evaluate the scale factor
4098 }
4099 totalGutterWidth = (maxCol-1) * gutterWidth;
4100 scaleFactor = Math.min( (areaWidth - totalGutterWidth) / (maxCol*tnWidth), 1); // no upscale
4101 newAreaWidth = (maxCol*tnWidth) + totalGutterWidth;
4102 }
4103
4104
4105 G.GOM.lastFullRow = 0 ; // display at leat 1 row (even if not full)
4106 var lastPosY = 0;
4107 var row = 0;
4108
4109 tnWidth = tnWidth * scaleFactor;
4110 var contentWidth = tnWidth - borderWidth;
4111 var tnHeight = G.tn.defaultSize.getOuterHeight() * scaleFactor + G.tn.labelHeight.get();
4112 var contentHeight = G.tn.defaultSize.getOuterHeight() * scaleFactor - borderHeight;
4113
4114 // loop to position and to set size of all thumbnails
4115 for( var i = 0; i < nbTn ; i++ ) {
4116 if( curPosY == 0 ) {
4117 curPosX = curCol * (tnWidth + gutterWidth)
4118 cols[curCol] = curPosX;
4119 w = curPosX + tnWidth;
4120 }
4121 else {
4122 curPosX = cols[curCol];
4123 }
4124
4125 var x = curPosX;
4126 if( G.O.RTL ) {
4127 x = parseInt(newAreaWidth) - curPosX - tnWidth;
4128 }
4129
4130 // MANDATORY : set thumbnail position AND size
4131 var curTn=G.GOM.items[i];
4132 curTn.top = curPosY;
4133 curTn.left = x;
4134 curTn.height = tnHeight;
4135 curTn.width = tnWidth;
4136 // image size
4137 if( G.O.thumbnailAlignment == 'fillWidth' ) {
4138 curTn.resizedContentWidth = contentWidth;
4139 curTn.resizedContentHeight = contentHeight;
4140 }
4141 curTn.row = row;
4142 lastPosY = curPosY;
4143
4144 curCol++;
4145 if( curCol >= maxCol ){
4146 // new line
4147 curCol = 0;
4148 curPosY += tnHeight + gutterHeight;
4149 G.GOM.lastFullRow = row;
4150 row++;
4151 }
4152 }
4153 G.GOM.displayArea.width = w;
4154 return true;
4155 }
4156
4157
4158 //----- Display the thumbnails according to the calculated layout
4159 function GalleryDisplayPart1( forceTransition ) {
4160 if( G.CSStransformName == null ) {
4161 G.$E.conTn.css( 'left' , '0px');
4162 }
4163 else {
4164 G.$E.conTn.css( G.CSStransformName , 'none');
4165 }
4166 CacheViewport();
4167 }
4168
4169 function CacheViewport() {
4170 G.GOM.cache.viewport = getViewport();
4171 G.GOM.cache.areaWidth = G.$E.conTnParent.width();
4172 G.GOM.cache.containerOffset = G.$E.conTnParent.offset();
4173 }
4174
4175
4176
4177 function GalleryDisplayPart2( forceTransition ) {
4178
4179 var nbTn = G.GOM.items.length;
4180 G.GOM.itemsDisplayed = 0;
4181 var threshold = 50;
4182 var cnt = 0; // counter for delay between each thumbnail display
4183
4184
4185 GalleryRenderGetInterval();
4186
4187 for( var i = 0; i < nbTn ; i++ ) {
4188 var curTn = G.GOM.items[i];
4189 if( i >= G.GOM.displayInterval.from && cnt < G.GOM.displayInterval.len ) {
4190 curTn.inDisplayArea = true;
4191 if( forceTransition ) {
4192 curTn.neverDisplayed = true;
4193 }
4194 G.GOM.itemsDisplayed++;
4195 cnt++;
4196 }
4197 else{
4198 curTn.inDisplayArea = false;
4199 }
4200 }
4201
4202 // bottom of the gallery (pagination, more button...)
4203 GalleryBottomManage();
4204
4205 var tnToDisplay = [];
4206 var tnToReDisplay = [];
4207
4208 G.GOM.clipArea.top = -1;
4209 cnt = 0 ;
4210 var lastTnIdx = -1;
4211 G.GOM.clipArea.height = 0;
4212 // NOTE: loop always the whole GOM.items --> in case an already displayed thumbnail needs to be removed
4213 for( var i = 0; i < nbTn ; i++ ) {
4214 var curTn = G.GOM.items[i];
4215 if( curTn.inDisplayArea ) {
4216 if( G.GOM.clipArea.top == -1 ) {
4217 G.GOM.clipArea.top = curTn.top;
4218 }
4219 if( (curTn.top - G.GOM.clipArea.top) <= -1 ) {
4220 // with mosaic layout, the first thumbnail may not give the top position
4221 G.GOM.clipArea.top = curTn.top;
4222 }
4223
4224 G.GOM.clipArea.height = Math.max( G.GOM.clipArea.height, curTn.top-G.GOM.clipArea.top + curTn.height);
4225
4226 if( curTn.neverDisplayed ) {
4227 // thumbnail is not displayed -> check if in viewport to display or not
4228 var top = G.GOM.cache.containerOffset.top + (curTn.top - G.GOM.clipArea.top);
4229 // var left=containerOffset.left+curTn.left;
4230 if( (top + curTn.height) >= (G.GOM.cache.viewport.t - threshold) && top <= (G.GOM.cache.viewport.t + G.GOM.cache.viewport.h + threshold) ) {
4231 // build thumbnail
4232 var item = G.I[curTn.thumbnailIdx];
4233 if( item.$elt == null ) {
4234 ThumbnailBuild( item, curTn.thumbnailIdx, i, (i+1) == nbTn );
4235 }
4236 tnToDisplay.push({idx:i, delay:cnt});
4237 cnt++;
4238 }
4239 }
4240 else {
4241 tnToReDisplay.push({idx: i, delay: 0});
4242 }
4243 // G.GOM.itemsDisplayed++;
4244 lastTnIdx = i;
4245 }
4246 else {
4247 curTn.displayed = false;
4248 var item = G.I[curTn.thumbnailIdx];
4249 if( item.$elt != null ){
4250 item.$elt.css({ opacity: 0, display: 'none' });
4251 }
4252 }
4253 }
4254
4255 var areaWidth = G.$E.conTnParent.width();
4256
4257 // set gallery area really used size
4258 // if( G.GOM.displayArea.width != G.GOM.displayAreaLast.width || G.GOM.displayArea.height != G.GOM.displayAreaLast.height ) {
4259 if( G.GOM.displayArea.width != G.GOM.displayAreaLast.width || G.GOM.clipArea.height != G.GOM.displayAreaLast.height ) {
4260 G.$E.conTn.width( G.GOM.displayArea.width ).height( G.GOM.clipArea.height );
4261 G.GOM.displayAreaLast.width = G.GOM.displayArea.width;
4262 G.GOM.displayAreaLast.height = G.GOM.clipArea.height;
4263 // G.GOM.displayAreaLast.height=G.GOM.displayArea.height-G.GOM.clipArea.top;
4264 }
4265
4266 if( areaWidth != G.$E.conTnParent.width() ) {
4267 // gallery area width changed since layout calculation (for example when a scrollbar appeared)
4268 // so we need re-calculate the layout before displaying the thumbnails
4269 G.GOM.cache.areaWidth = G.$E.conTnParent.width();
4270 GallerySetLayout();
4271 GalleryDisplayPart1( forceTransition );
4272 GalleryDisplayPart2( forceTransition );
4273 return;
4274 }
4275
4276 // counter of not displayed images (is displayed on the last thumbnail)
4277 if( G.layout.support.rows ) {
4278 if( G.galleryDisplayMode.Get() == 'ROWS' || (G.galleryDisplayMode.Get() == 'FULLCONTENT' && G.galleryLastRowFull.Get() && G.GOM.lastFullRow != -1) ){
4279 if( lastTnIdx < (nbTn - 1) ) {
4280 G.GOM.lastDisplayedIdxNew = lastTnIdx;
4281 }
4282 else {
4283 G.GOM.lastDisplayedIdxNew =- 1;
4284 }
4285 // remove last displayed counter
4286 if( G.GOM.lastDisplayedIdx != -1 ) {
4287 var item = G.I[G.GOM.items[G.GOM.lastDisplayedIdx].thumbnailIdx];
4288 item.$getElt('.nGY2GThumbnailIconsFullThumbnail').html('');
4289 }
4290 }
4291 }
4292
4293
4294 // batch set position (and display animation) to all thumbnails
4295 // first display newly built thumbnails
4296 var nbBuild = tnToDisplay.length;
4297 G.GOM.thumbnails2Display=[];
4298 for( var i = 0; i < nbBuild ; i++ ) {
4299 // ThumbnailSetPosition(tnToDisplay[i].idx, tnToDisplay[i].delay+10);
4300 ThumbnailSetPosition(tnToDisplay[i].idx, i);
4301 }
4302
4303 // then re-position already displayed thumbnails
4304 var n = tnToReDisplay.length;
4305 for( var i = 0; i < n ; i++ ) {
4306 // ThumbnailSetPosition(tnToReDisplay[i].idx, nbBuild+1);
4307 ThumbnailSetPosition(tnToReDisplay[i].idx, i);
4308 }
4309
4310 ThumbnailDisplayAnimBatch();
4311
4312 if( G.tn.opt.Get('displayTransition') == 'NONE' ) {
4313 G.galleryResizeEventEnabled = true;
4314 // GalleryThumbnailSliderBuildAndStart(); // image slider on last displayed thumbnail
4315 TriggerCustomEvent('galleryDisplayed');
4316 }
4317 else {
4318 // setTimeout(function() {
4319 requestTimeout( function() {
4320 // change value after the end of the display transistion of the newly built thumbnails
4321 G.galleryResizeEventEnabled = true;
4322 // GalleryThumbnailSliderBuildAndStart(); // image slider on last displayed thumbnail
4323 TriggerCustomEvent('galleryDisplayed');
4324 }, nbBuild * G.tn.opt.Get('displayInterval'));
4325 }
4326
4327 }
4328
4329
4330 // Thumbnail: set the new position
4331 function ThumbnailSetPosition( GOMidx, cnt ) {
4332 var newTop= 0;
4333 var curTn= G.GOM.items[GOMidx];
4334 var idx= G.GOM.items[GOMidx].thumbnailIdx;
4335 var item= G.I[idx];
4336
4337 if( curTn.neverDisplayed ) {
4338 // thumbnail is built but has never been displayed (=first display)
4339 var top = curTn.top - G.GOM.clipArea.top;
4340 if( G.tn.opt.Get('stacks') > 0 ) {
4341 // we have stacks -> do not display them here. They will be displayed at the end of the display animation
4342 item.$elt.last().css({ display: 'block'});
4343 item.$elt.css({ top: top , left: curTn.left });
4344 }
4345 else {
4346 item.$elt.css({ display: 'block', top: top , left: curTn.left });
4347 }
4348 newTop=top;
4349
4350 // display the image of the thumbnail when fully loaded
4351 if( G.O.thumbnailWaitImageLoaded === true ) {
4352 var gi_imgLoad = ngimagesLoaded( item.$getElt('.nGY2TnImg2') );
4353 gi_imgLoad.on( 'progress', function( instance, image ) {
4354 if( image.isLoaded ) {
4355 var albumIdx = image.img.getAttribute('data-albumidx');
4356 if( albumIdx == G.GOM.albumIdx ) {
4357 // ignore event if not on current album
4358 var idx = image.img.getAttribute('data-idx');
4359 G.I[idx].ThumbnailImageReveal();
4360 }
4361 }
4362 });
4363 }
4364 // display the thumbnail
4365 ThumbnailAppear(GOMidx, cnt);
4366
4367 curTn.displayed = true;
4368 curTn.neverDisplayed = false;
4369 }
4370 else {
4371 var topOld = G.GOM.cache.containerOffset.top + item.top;
4372 var top = G.GOM.cache.containerOffset.top + (curTn.top - G.GOM.clipArea.top);
4373 newTop = curTn.top - G.GOM.clipArea.top;
4374 var vp = G.GOM.cache.viewport;
4375 if( G.O.thumbnailDisplayOutsideScreen || ( ( (topOld + curTn.height) >= (vp.t - vp.h) && topOld <= (vp.t + vp.h * 4) ) ||
4376 ( (top + curTn.height) >= (vp.t - vp.h) && top <= (vp.t + vp.h * 4) ) ) ) {
4377 // thumbnail positioned in enlarged viewport (viewport + 4 x viewport height) (v1.5: changed from 2 to 4)
4378 if( curTn.displayed ) {
4379 // thumbnail is displayed
4380 if( item.top != curTn.top || item.left != curTn.left ) {
4381 // set position
4382 if( G.O.galleryResizeAnimation == true ) {
4383 // with transition
4384 var tweenable = new NGTweenable();
4385 tweenable.tween({
4386 from: { top: item.top, left: item.left, height: item.height, width: item.width },
4387 to: { top: newTop, left: curTn.left, height: curTn.height, width: curTn.width },
4388 attachment: { $e: item.$elt },
4389 duration: 100,
4390 delay: cnt * G.tn.opt.Get('displayInterval') / 5,
4391 // easing: 'easeInOutQuad',
4392 easing: 'easeOutQuart',
4393 step: function (state, att) {
4394 // window.ng_draf( function() {
4395 att.$e.css(state);
4396 // });
4397 },
4398 finish: function (state, att) {
4399 var _this=this;
4400 // window.ng_draf( function() {
4401 _this.dispose();
4402 // });
4403 }
4404 });
4405 }
4406 else {
4407 // set position without transition
4408 // item.$elt.css({ top: curTn.top , left: curTn.left });
4409 item.$elt.css({ top: newTop , left: curTn.left });
4410 }
4411 }
4412 }
4413 else {
4414 // re-display thumbnail
4415 curTn.displayed = true;
4416 // item.$elt.css({ display: 'block', top: curTn.top , left: curTn.left, opacity:1 });
4417 item.$elt.css({ display: 'block', top: newTop, left: curTn.left, opacity: 1 });
4418 ThumbnailAppearFinish(item);
4419 }
4420 }
4421 else {
4422 // undisplay thumbnail if not in viewport+margin --> performance gain
4423 curTn.displayed = false;
4424 item.$elt.css({ display: 'none'});
4425 }
4426 }
4427 item.left = curTn.left;
4428 item.top = newTop;
4429
4430 // set new size if changed
4431 if( item.width != curTn.width || item.height != curTn.height ) {
4432 item.$elt.css({ width: curTn.width , height: curTn.height });
4433 item.width = curTn.width;
4434 item.height = curTn.height;
4435
4436 // if( curTn.resizedContentWidth > 0 ) {
4437 // resize also the content (=image)
4438 if( item.resizedContentWidth != curTn.resizedContentWidth || item.resizedContentHeight != curTn.resizedContentHeight ) {
4439 if( item.kind == 'albumUp' ) {
4440 // item.$getElt('.nGY2GThumbnailAlbumUp').css({'height': curTn.resizedContentHeight, 'width': curTn.resizedContentWidth});
4441 }
4442 else {
4443 item.$getElt('.nGY2GThumbnailImage').css({'height': curTn.resizedContentHeight, 'width': curTn.resizedContentWidth});
4444
4445 if( G.layout.engine == 'JUSTIFIED' ) {
4446 item.$getElt('.nGY2GThumbnailImg').css({'height': curTn.resizedContentHeight, 'width': curTn.resizedContentWidth});
4447 }
4448 }
4449 item.resizedContentWidth = curTn.resizedContentWidth;
4450 item.resizedContentHeight = curTn.resizedContentHeight;
4451 }
4452 }
4453
4454
4455 // add counter of remaining (not displayed) images
4456 if( G.GOM.lastDisplayedIdxNew == GOMidx && G.layout.support.rows ) {
4457 if( (G.galleryDisplayMode.Get() == 'ROWS' && G.galleryMaxRows.Get() > 0) || (G.galleryDisplayMode.Get() == 'FULLCONTENT' && G.galleryLastRowFull.Get() && G.GOM.lastFullRow != -1) ){
4458 // number of items
4459 var nb = G.GOM.items.length - GOMidx - 1;
4460 if( item.albumID != '0' && G.O.thumbnailLevelUp ) {
4461 nb--;
4462 }
4463
4464 if( nb > 0 ) {
4465 // display counter
4466 if( G.O.thumbnailOpenImage || G.O.thumbnailSliderDelay > 0 ) {
4467 item.$getElt('.nGY2GThumbnailIconsFullThumbnail').html( '+' + nb);
4468 }
4469
4470 // if( G.layout.engine == 'GRID' && G.GOM.slider.hostItem != G.GOM.NGY2Item(GOMidx) ) {
4471 // image slider on last displayed thumbnail
4472 if( G.GOM.slider.hostItem != G.GOM.NGY2Item(GOMidx) ) {
4473
4474 // set current slider back to initial content
4475 GalleryThumbnailSliderSetContent( G.GOM.slider.hostItem );
4476 // new slider
4477 G.GOM.slider.hostIdx = GOMidx;
4478 G.GOM.slider.hostItem = G.GOM.NGY2Item(GOMidx);
4479 G.GOM.slider.nextIdx = GOMidx;
4480 G.GOM.slider.currentIdx = GOMidx;
4481 GalleryThumbnailSliderBuildAndStart(); // image slider on last displayed thumbnail
4482 // GalleryThumbnailSliderSetNextContent();
4483 }
4484 }
4485 else {
4486 // reset slider content to initial content because all thumbnails are displayed
4487 GalleryThumbnailSliderSetContent( G.GOM.slider.hostItem );
4488 G.GOM.slider.hostIdx = -1;
4489 }
4490
4491 G.GOM.lastDisplayedIdx = GOMidx;
4492 }
4493 }
4494
4495 }
4496
4497 // ---------------------
4498 // replace image on last thumbnails with not displayed ones (mode ROWS or FULLCONTENT with galleryLastRowFull enabled)
4499 // function GalleryLastThumbnailSlideImage() {
4500 function GalleryThumbnailSliderBuildAndStart() {
4501
4502 if( G.O.thumbnailSliderDelay == 0 || G.GOM.slider.hostIdx == -1 ) {
4503 return;
4504 }
4505 clearTimeout(G.GOM.slider.timerID);
4506
4507 var item = G.GOM.slider.hostItem;
4508
4509 // dupplicate image layer -> for the next image
4510 if( item.$getElt('.nGY2TnImgNext').length == 0 ) {
4511 item.$getElt('.nGY2TnImg').clone().removeClass('nGY2TnImg').addClass('nGY2TnImgNext').insertAfter(item.$getElt('.nGY2TnImg'));
4512 item.$getElt('.nGY2TnImgBack').clone().removeClass('nGY2TnImgBack').addClass('nGY2TnImgBackNext').insertAfter(item.$getElt('.nGY2TnImg', true));
4513 item.$getElt('.nGY2GThumbnailImage', true); // important -> refresh the cache
4514 item.$getElt('.nGY2GThumbnailImg', true); // important -> refresh the cache
4515 }
4516
4517 item.CSSTransformSet('.nGY2TnImgNext', 'translateX', '100%', true);
4518 item.CSSTransformApply( '.nGY2TnImgNext' );
4519 item.CSSTransformSet('.nGY2TnImgBackNext', 'translateX', '100%', true);
4520 item.CSSTransformApply( '.nGY2TnImgBackNext' );
4521
4522 GalleryThumbnailSliderSetNextContent();
4523
4524 // clearTimeout(G.GOM.slider.timerID);
4525 // G.GOM.slider.timerID = setTimeout(function(){ GalleryThumbnailSliderStartTransition() }, G.O.thumbnailSliderDelay);
4526 G.GOM.slider.timerID = requestTimeout(function(){ GalleryThumbnailSliderStartTransition() }, G.O.thumbnailSliderDelay);
4527 }
4528
4529
4530 function GalleryThumbnailSliderSetNextContent() {
4531
4532 G.GOM.slider.nextIdx++;
4533 if( G.GOM.slider.nextIdx >= G.GOM.items.length ) {
4534 G.GOM.slider.nextIdx = G.GOM.slider.hostIdx;
4535 }
4536
4537 // new image
4538 var newItem = G.GOM.NGY2Item(G.GOM.slider.nextIdx);
4539 var imgBlurred = G.emptyGif;
4540 var bgImg = "url('" + G.emptyGif + "')";
4541 if( newItem.imageDominantColors != null ) {
4542 imgBlurred = newItem.imageDominantColors;
4543 bgImg = "url('" + newItem.imageDominantColors + "')";
4544 }
4545 G.GOM.slider.hostItem.$getElt('.nGY2TnImgBackNext', true).css({'background-image': bgImg, opacity: 1 });
4546 G.GOM.slider.hostItem.$getElt('.nGY2TnImgNext', true).css({ 'background-image': "url('" + newItem.thumbImg().src + "')", opacity: 1 });
4547 G.GOM.slider.hostItem.$getElt('.nGY2TnImgNext .nGY2GThumbnailImg', true).attr('src', newItem.thumbImg().src );
4548
4549
4550 }
4551
4552 // thumbnail slider - transition from one image to the next one
4553 function GalleryThumbnailSliderStartTransition() {
4554
4555 if( G.GOM.slider.hostItem.$getElt() != null ) {
4556
4557 // slider transition
4558 var tweenable = new NGTweenable();
4559 G.GOM.slider.tween = tweenable;
4560 tweenable.tween({
4561 from: { 'left': 100 },
4562 to: { 'left': 0 },
4563 duration: 800,
4564 delay: 0,
4565 // easing: 'easeInOutQuad',
4566 easing: 'easeOutQuart',
4567
4568 step: function (state) {
4569 if( G.GOM.slider.hostItem.$getElt() == null ) {
4570 // the thumbnail may have been destroyed since the start of the animation
4571 G.GOM.slider.tween.stop(false);
4572 return;
4573 }
4574
4575 // window.ng_draf( function() {
4576 // slide current content
4577 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgBack', 'translateX', -(100 - state.left) + '%');
4578 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgBack' );
4579 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImg', 'translateX', -(100 - state.left) + '%');
4580 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImg' );
4581
4582 // slide new content
4583 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgBackNext', 'translateX', state.left + '%');
4584 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgBackNext' );
4585 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgNext', 'translateX', state.left + '%');
4586 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgNext' );
4587 // });
4588
4589
4590 },
4591 finish: function (state) {
4592 if( G.GOM.slider.hostItem.$getElt() == null ) {
4593 // the thumbnail may be destroyed since the start of the animation
4594 return;
4595 }
4596
4597 if( G.GOM.NGY2Item(G.GOM.slider.nextIdx) == null ) { return; } // item does not exist anymore
4598
4599 // window.ng_draf( function() {
4600 // set new content as current content
4601 GalleryThumbnailSliderSetContent( G.GOM.NGY2Item(G.GOM.slider.nextIdx) );
4602 G.GOM.slider.currentIdx = G.GOM.slider.nextIdx;
4603 GalleryThumbnailSliderSetNextContent();
4604
4605 clearTimeout(G.GOM.slider.timerID);
4606 // G.GOM.slider.timerID=setTimeout(function(){ GalleryThumbnailSliderStartTransition() }, G.O.thumbnailSliderDelay);
4607 G.GOM.slider.timerID = requestTimeout(function(){ GalleryThumbnailSliderStartTransition() }, G.O.thumbnailSliderDelay);
4608 // });
4609 }
4610 });
4611 }
4612 }
4613
4614 // set main content of the thumbnail hosting the slider
4615 // hide the elements for the next content of the slider
4616 function GalleryThumbnailSliderSetContent( ngy2itemContent ) {
4617 if( G.GOM.slider.hostIdx == -1 ) { return; }
4618
4619 if( G.GOM.slider.tween != null ) {
4620 if( G.GOM.slider.tween._isTweening == true ) {
4621 G.GOM.slider.tween.stop(false);
4622 }
4623 }
4624
4625 var bgImg = "url('" + G.emptyGif + "')";
4626 if( ngy2itemContent.imageDominantColors != null ) {
4627 bgImg = "url('" + ngy2itemContent.imageDominantColors + "')";
4628 }
4629 G.GOM.slider.hostItem.$getElt('.nGY2TnImgBack').css('background-image', bgImg);
4630 G.GOM.slider.hostItem.$getElt('.nGY2TnImg').css('background-image', "url('" + ngy2itemContent.thumbImg().src + "')" );
4631 G.GOM.slider.hostItem.$getElt('.nGY2TnImg .nGY2GThumbnailImg').attr('src', ngy2itemContent.thumbImg().src );
4632
4633 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgBack', 'translateX', '0');
4634 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgBack' );
4635 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImg', 'translateX', '0');
4636 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImg' );
4637
4638 // place the containers for the next image slider outside of the thumbnail (=hidden)
4639 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgBackNext', 'translateX', '100%', true);
4640 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgBackNext' );
4641 G.GOM.slider.hostItem.CSSTransformSet('.nGY2TnImgNext', 'translateX', '100%', true);
4642 G.GOM.slider.hostItem.CSSTransformApply( '.nGY2TnImgNext' );
4643
4644 // set new title and description
4645 if( G.O.thumbnailLabel.get('display') == true ) {
4646 var icons = G.O.icons.thumbnailAlbum;
4647 if( ngy2itemContent.kind != 'album' ) {
4648 icons = G.O.icons.thumbnailImage;
4649 }
4650 G.GOM.slider.hostItem.$getElt('.nGY2GThumbnailTitle').html(icons + getThumbnailTitle(ngy2itemContent));
4651 G.GOM.slider.hostItem.$getElt('.nGY2GThumbnailDescription').html(icons + getTumbnailDescription(ngy2itemContent));
4652 }
4653 }
4654
4655
4656
4657 // Compute the height of the label part of a thumbnail (title+description, both single line)
4658 function ThumbnailGetLabelHeight() {
4659 var newElt = [],
4660 newEltIdx = 0;
4661
4662 // if( G.O.thumbnailLabel.get('display') == false && G.tn.toolbar.getWidth(item) <= 0 ) {
4663 if( G.O.thumbnailLabel.get('display') == false ) {
4664 return 0;
4665 }
4666
4667 var desc='';
4668 if( G.O.thumbnailLabel.get('displayDescription') == true ) {
4669 desc = 'aAzZjJ';
4670 }
4671
4672 // visibility set to hidden
4673 newElt[newEltIdx++] = '<div class="nGY2GThumbnail ' + G.O.theme + '" style="display:block;visibility:hidden;position:absolute;top:-9999px;left:-9999px;" ><div class="nGY2GThumbnailSub">';
4674 if( G.O.thumbnailLabel.get('display') == true ) {
4675 // Labels: title and description
4676 newElt[newEltIdx++] = ' <div class="nGY2GThumbnailLabel" '+ G.tn.style.getLabel() +'>';
4677 newElt[newEltIdx++] = ' <div class="nGY2GThumbnailAlbumTitle" '+G.tn.style.getTitle()+'>aAzZjJ</div>';
4678 if( G.O.thumbnailLabel.get('displayDescription') == true ) {
4679 newElt[newEltIdx++] = ' <div class="nGY2GThumbnailDescription" '+G.tn.style.getDesc()+'>'+'aAzZjJ'+'</div>';
4680 }
4681 newElt[newEltIdx++] = ' </div>';
4682 }
4683
4684 newElt[newEltIdx++] = '</div></div>';
4685
4686 var $newDiv = jQuery(newElt.join('')).appendTo(G.$E.conTn);
4687 var h = $newDiv.find('.nGY2GThumbnailLabel').outerHeight(true);
4688 $newDiv.remove();
4689
4690 return h;
4691 }
4692
4693 function ThumbnailBuildStacks( bgColor ) {
4694 var ns=G.tn.opt.Get('stacks');
4695 if( ns == 0 ) { return ''; }
4696
4697 var s='';
4698 for( var i=0; i<ns; i++ ) {
4699 s='<div class="nGY2GThumbnailStack " style="display:none;'+bgColor+'"></div>'+s;
4700 }
4701 return s;
4702 }
4703
4704 //----- Build one UP thumbnail (=navigation thumbnail)
4705 function ThumbnailBuildAlbumpUp( item, idx, GOMidx ) {
4706 var newElt = [],
4707 newEltIdx = 0;
4708
4709 var mp = '';
4710 if( G.O.thumbnailOpenImage === false ) {
4711 mp = 'cursor:default;'
4712 }
4713
4714 newElt[newEltIdx++] = ThumbnailBuildStacks('') + '<div class="nGY2GThumbnail" style="display:none;opacity:0;' + mp + '" >';
4715 newElt[newEltIdx++] = ' <div class="nGY2GThumbnailSub">';
4716
4717 var h=G.tn.defaultSize.getHeight(),
4718 w=G.tn.defaultSize.getWidth();
4719
4720 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>';
4721 // newElt[newEltIdx++] = ' <div class="nGY2GThumbnailAlbumUp" style="width:'+w+'px;height:'+h+'px;">'+G.O.icons.thumbnailAlbumUp+'</div>';
4722 newElt[newEltIdx++] = ' <div class="nGY2GThumbnailAlbumUp" >'+G.O.icons.thumbnailAlbumUp+'</div>';
4723 newElt[newEltIdx++] = ' </div>';
4724 newElt[newEltIdx++] = '</div>';
4725
4726 var $newDiv = jQuery(newElt.join('')).appendTo(G.$E.conTn); //.animate({ opacity: 1},1000, 'swing'); //.show('slow'); //.fadeIn('slow').slideDown('slow');
4727
4728 item.$elt = $newDiv;
4729 $newDiv.data('index', GOMidx);
4730 item.$getElt('.nGY2GThumbnailImg').data('index', GOMidx);
4731
4732 return;
4733 }
4734
4735
4736 //----- Build one thumbnail
4737 function ThumbnailBuild( item, idx, GOMidx, lastOne ) {
4738 item.eltTransform = [];
4739 item.eltFilter = [];
4740 item.hoverInitDone = false;
4741 item.$Elts = [];
4742
4743 if( item.kind == 'albumUp' ) {
4744 ThumbnailBuildAlbumpUp( item, idx, GOMidx);
4745 return;
4746 }
4747
4748 var newElt = [],
4749 newEltIdx = 0;
4750
4751 var mp = '';
4752 if( G.O.thumbnailOpenImage === false ) {
4753 mp = 'cursor:default;'
4754 }
4755
4756 // var src = encodeURI(item.thumbImg().src),
4757 var src = (item.thumbImg().src).replace(/'/g, "%27"), // replace single quote with %27
4758 sTitle = getThumbnailTitle(item);
4759
4760 // image background -> visible during image download
4761 var bg = '';
4762 var bgImg = "background-image: url('" + G.emptyGif + "');";
4763 if( item.imageDominantColors != null ) {
4764 // dominant colorS (blurred preview image)
4765 bgImg = "background-image: url('" + item.imageDominantColors + "');";
4766 }
4767 else {
4768 // dominant color -> background color
4769 if( item.imageDominantColor != null ) {
4770 bg = 'background-color:' + item.imageDominantColor + ';';
4771 }
4772 else {
4773 bgImg = '';
4774 }
4775 }
4776
4777 var op = 'opacity:1;';
4778 if( G.O.thumbnailWaitImageLoaded == true ) {
4779 op = 'opacity:0;';
4780 }
4781
4782 // ##### thumbnail containers (with stacks)
4783 newElt[newEltIdx++] = ThumbnailBuildStacks(bg) + '<div class="nGY2GThumbnail" style="display:none;opacity:0;' + mp + '"><div class="nGY2GThumbnailSub ' + ( G.O.thumbnailSelectable && item.selected ? "nGY2GThumbnailSubSelected" : "" ) + '">';
4784
4785
4786 // image size
4787 var w = G.tn.settings.getW();
4788 var h = G.tn.settings.getH();
4789 if( G.tn.settings.getMosaic() !== null ) {
4790 // mosaic layout ->
4791 w = G.GOM.items[GOMidx].width;
4792 h = G.GOM.items[GOMidx].height;
4793 }
4794
4795 var bgSize = 'contain';
4796 if( G.tn.opt.Get('crop') ) {
4797 bgSize = 'cover';
4798 }
4799
4800 // ##### layer for image background (color, dominant color, blurred preview)
4801 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;";
4802 newElt[newEltIdx++]='<div class="nGY2GThumbnailImage nGY2TnImgBack" style="' + s1 + '"></div>';
4803
4804 // #### layer for image
4805 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;";
4806 newElt[newEltIdx++]='<div class="nGY2GThumbnailImage nGY2TnImg" style="' + s2 + '">';
4807 newElt[newEltIdx++]=' <img class="nGY2GThumbnailImg nGY2TnImg2" src="' + src + '" alt="' + sTitle + '" style="opacity:0;" data-idx="' + idx + '" data-albumidx="' + G.GOM.albumIdx + '" >';
4808 newElt[newEltIdx++]='</div>';
4809
4810 // ##### layer for user customization purposes
4811 newElt[newEltIdx++]='<div class="nGY2GThumbnailCustomLayer"></div>';
4812
4813 // ##### layer for labels (title + description and their icons)
4814 if( G.O.thumbnailLabel.get('display') == true ) {
4815 // Labels: title and description
4816 newElt[newEltIdx++]= ' <div class="nGY2GThumbnailLabel" '+ G.tn.style.getLabel(item) + '>';
4817 if( item.kind == 'album' ) {
4818 // album kind
4819 newElt[newEltIdx++]= ' <div class="nGY2GThumbnailTitle nGY2GThumbnailAlbumTitle" ' + G.tn.style.getTitle() + '>' + G.O.icons.thumbnailAlbum + sTitle + '</div>';
4820 }
4821 else {
4822 // image/media kind
4823 newElt[newEltIdx++]= ' <div class="nGY2GThumbnailTitle nGY2GThumbnailImageTitle" ' + G.tn.style.getTitle() + '>' + G.O.icons.thumbnailImage + sTitle + '</div>';
4824 }
4825 newElt[newEltIdx++]= ' <div class="nGY2GThumbnailDescription" ' + G.tn.style.getDesc() + '>' + getTumbnailDescription(item) + '</div>';
4826 newElt[newEltIdx++]= ' </div>';
4827 }
4828
4829 // ##### layer for tools
4830 newElt[newEltIdx++] = ThumbnailBuildTools(item, lastOne);
4831
4832 // close containers
4833 newElt[newEltIdx++]='</div></div>';
4834
4835 var $newDiv =jQuery(newElt.join('')).appendTo(G.$E.conTn);
4836
4837 item.$elt=$newDiv;
4838 $newDiv.data('index',GOMidx);
4839 item.$getElt('.nGY2GThumbnailImg').data('index',GOMidx);
4840
4841 // Custom init function
4842 var fu=G.O.fnThumbnailInit;
4843 if( fu !== null ) {
4844 typeof fu == 'function' ? fu($newDiv, item, GOMidx) : window[fu]($newDiv, item, GOMidx);
4845 }
4846
4847 if( item.title != 'image gallery by nanogallery2 [build]' ) {
4848 ThumbnailOverInit(GOMidx);
4849 }
4850
4851 return ;
4852 }
4853
4854
4855 // Thumbnail layer for tools (toolbars and counter)
4856 function ThumbnailBuildTools( item, lastThumbnail ) {
4857
4858 // toolbars
4859 var tb = ThumbnailBuildToolbarOne(item, 'topLeft') + ThumbnailBuildToolbarOne(item, 'topRight') + ThumbnailBuildToolbarOne(item, 'bottomLeft') + ThumbnailBuildToolbarOne(item, 'bottomRight');
4860
4861 // counter of not displayed images
4862 tb += '<div class="nGY2GThumbnailIconsFullThumbnail"></div>';
4863
4864 return tb;
4865 }
4866
4867 function ThumbnailBuildToolbarOne( item, position ) {
4868 var toolbar = '';
4869 var tb = G.tn.toolbar.get(item);
4870 var width = { xs:0, sm:1, me:2, la:3, xl:4 };
4871 var cnt = 0;
4872
4873 if( tb[position] != '' ) {
4874 var pos='top: 0; right: 0; text-align: right;'; // 'topRight' and default
4875 switch( position ) {
4876 case 'topLeft':
4877 pos = 'top: 0; left: 0; text-align: left;';
4878 break;
4879 case 'bottomRight':
4880 pos = 'bottom: 0; right: 0; text-align: right;';
4881 break;
4882 case 'bottomLeft':
4883 pos = 'bottom: 0; left: 0; text-align: left;';
4884 break;
4885 }
4886
4887 toolbar += ' <ul class="nGY2GThumbnailIcons" style="' + pos + '">';
4888
4889 var icons = tb[position].split(',');
4890 var nb = icons.length;
4891 for( var i = 0; i < nb; i++ ) {
4892 var icon = icons[i].replace(/^\s*|\s*$/, ''); //trim trailing/leading whitespace
4893
4894 var minWidth = icon.substring(0,2).toLowerCase();
4895 var tIcon = icon;
4896 var display = true;
4897 if( /xs|sm|me|la|xl/i.test(minWidth) ) {
4898 // check visbility (depending on screen width)
4899 if( width[minWidth] > width[G.GOM.curWidth] ) {
4900 display = false;
4901 }
4902 tIcon = icon.substring(2);
4903 }
4904
4905 if( display ) {
4906 var sp=(i+1<nb ? '&nbsp;' :'');
4907 switch( tIcon ) {
4908 case 'COUNTER':
4909 if( item.kind == 'album' ) {
4910 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="">';
4911 toolbar += ' <div class="nGY2GThumbnailIconImageCounter"></div>';
4912 toolbar += ' <div class="nGY2GThumbnailIconText">' + G.O.icons.thumbnailCounter+Math.max((item.getContentLength(false)),item.numberItems) + sp + '</div>';
4913 toolbar += ' </li>';
4914 cnt++;
4915 }
4916 break;
4917 case 'COUNTER2':
4918 if( item.kind == 'album' ) {
4919 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="">';
4920 toolbar += ' <div class="nGY2GThumbnailIconTextBadge">' + G.O.icons.thumbnailCounter+Math.max((item.getContentLength(false)),item.numberItems) + sp + '</div>';
4921 toolbar += ' </li>';
4922 cnt++;
4923 }
4924 break;
4925 case 'SHARE':
4926 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="' + tIcon + '">';
4927 toolbar += ' <div>' + G.O.icons.thumbnailShare + '</div>';
4928 toolbar += ' </li>';
4929 cnt++;
4930 break;
4931 case 'DOWNLOAD':
4932 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="' + tIcon + '">';
4933 toolbar += ' <div>' + G.O.icons.thumbnailDownload + '</div>';
4934 toolbar += ' </li>';
4935 cnt++;
4936 break;
4937 case 'INFO':
4938 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="' + tIcon + '">';
4939 toolbar += ' <div>' + G.O.icons.thumbnailInfo + '</div>';
4940 toolbar += ' </li>';
4941 cnt++;
4942 break;
4943 case 'CART':
4944 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="' + tIcon + '">';
4945 toolbar += ' <div>' + G.O.icons.thumbnailCart + '</div>';
4946 toolbar += ' </li>';
4947 cnt++;
4948 break;
4949 case 'DISPLAY':
4950 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="DISPLAY">';
4951 toolbar += ' <div class="nGY2GThumbnailIconImageShare">' + G.O.icons.thumbnailDisplay + '</div>';
4952 toolbar += ' </li>';
4953 cnt++;
4954 break;
4955 case 'CUSTOM1':
4956 case 'CUSTOM2':
4957 case 'CUSTOM3':
4958 case 'CUSTOM4':
4959 case 'CUSTOM5':
4960 case 'CUSTOM6':
4961 case 'CUSTOM7':
4962 case 'CUSTOM8':
4963 case 'CUSTOM9':
4964 case 'CUSTOM10':
4965 var cust = tIcon.replace('CUSTOM', '');
4966 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="' + tIcon.toLowerCase() + '">';
4967 toolbar += ' <div class="nGY2GThumbnailIconImageShare">' + G.O.icons['thumbnailCustomTool' + cust] + '</div>';
4968 toolbar += ' </li>';
4969 cnt++;
4970 break;
4971 case 'FEATURED':
4972 if( item.featured === true ) {
4973 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="">';
4974 toolbar += ' <div class="nGY2GThumbnailIconImageFeatured">' + G.O.icons.thumbnailFeatured + '</div>';
4975 toolbar += ' </li>';
4976 cnt++;
4977 }
4978 break;
4979 case 'SELECT':
4980 if( G.O.thumbnailSelectable == true ) {
4981 toolbar += ' <li class="nGY2GThumbnailIcon" data-ngy2action="TOGGLESELECT">';
4982 if( item.selected === true ) {
4983 toolbar += ' <div class="nGY2GThumbnailIconImageSelect nGY2ThumbnailSelected">' + G.O.icons.thumbnailSelected + '</div>';
4984 }
4985 else {
4986 toolbar += ' <div class="nGY2GThumbnailIconImageSelect nGY2ThumbnailUnselected">' + G.O.icons.thumbnailUnselected + '</div>';
4987 }
4988 toolbar += ' </li>';
4989 cnt++;
4990 }
4991 break;
4992 }
4993 }
4994 }
4995 toolbar += ' </ul>';
4996 }
4997
4998 if( cnt > 0 ) {
4999 return toolbar;
5000 }
5001 else {
5002 return '';
5003 }
5004 }
5005
5006 function getThumbnailTitle( item ) {
5007
5008 var sTitle = item.title;
5009 if( G.O.thumbnailLabel.get('display') == true ) {
5010 if( sTitle === undefined || sTitle.length == 0 ) { sTitle = '&nbsp;'; }
5011
5012 if( G.i18nTranslations.thumbnailImageTitle != '' ) {
5013 sTitle = G.i18nTranslations.thumbnailImageTitle;
5014 }
5015 var ml = G.O.thumbnailLabel.get('titleMaxLength');
5016 if( ml > 3 && sTitle.length > ml ){
5017 sTitle = sTitle.substring(0, ml) + '...';
5018 }
5019 }
5020
5021 return sTitle;
5022 }
5023
5024 function getTumbnailDescription( item ) {
5025 var sDesc = '';
5026 if( G.O.thumbnailLabel.get('displayDescription') == true ) {
5027 if( item.kind == 'album' ) {
5028 if( G.i18nTranslations.thumbnailImageDescription != '' ) {
5029 sDesc = G.i18nTranslations.thumbnailAlbumDescription;
5030 }
5031 else {
5032 sDesc = item.description;
5033 }
5034 }
5035 else {
5036 if( G.i18nTranslations.thumbnailImageDescription != '' ) {
5037 sDesc = G.i18nTranslations.thumbnailImageDescription;
5038 }
5039 else {
5040 sDesc = item.description;
5041 }
5042 }
5043 var ml = G.O.thumbnailLabel.get('descriptionMaxLength');
5044 if( ml > 3 && sDesc.length > ml ){
5045 sDesc = sDesc.substring(0, ml) + '...';
5046 }
5047 if( sDesc.length == 0 ) {
5048 sDesc = '&nbsp;';
5049 }
5050 }
5051
5052 return sDesc;
5053 }
5054
5055
5056
5057 // Retrieve the maximum number of thumbnails that fits in one row
5058 function NbThumbnailsPerRow( areaWidth ) {
5059 var tnW = G.tn.defaultSize.getOuterWidth();
5060
5061 var nbMaxTn = 0;
5062 if( G.O.thumbnailAlignment == 'justified' ) {
5063 nbMaxTn = Math.floor((areaWidth)/(tnW));
5064 }
5065 else {
5066 nbMaxTn = Math.floor((areaWidth + G.tn.opt.Get('gutterWidth'))/(tnW + G.tn.opt.Get('gutterWidth')));
5067 }
5068
5069 if( G.O.maxItemsPerLine >0 && nbMaxTn > G.O.maxItemsPerLine ) {
5070 nbMaxTn = G.O.maxItemsPerLine;
5071 }
5072
5073 if( nbMaxTn < 1 ) { nbMaxTn = 1; }
5074
5075 return nbMaxTn
5076 }
5077
5078 // Thumbnail display animation
5079 function ThumbnailAppear( n, cnt ) {
5080 var curTn = G.GOM.items[n];
5081 var item = G.I[curTn.thumbnailIdx];
5082
5083
5084 if( G.tn.opt.Get('displayTransition') == 'NONE' ) {
5085 item.$elt.css({ opacity: 1 });
5086 ThumbnailAppearFinish( item );
5087 }
5088 else {
5089 if( item.$elt == null ) { return; }
5090 var top = G.GOM.cache.containerOffset.top + ( curTn.top - G.GOM.clipArea.top );
5091 var vp = G.GOM.cache.viewport;
5092 if( (top + (curTn.top - G.GOM.clipArea.top)) >= (vp.t - 50) && top <= (vp.t + vp.h + 50) ) {
5093 // display animation only if in the current viewport
5094 var delay = cnt * G.tn.opt.Get('displayInterval');
5095 if( G.tn.opt.Get('displayTransition') == 'CUSTOM' ) {
5096 if( G.GOM.curNavLevel == 'lN' ) {
5097 G.O.fnThumbnailDisplayEffect(item.$elt, item, n, delay);
5098 }
5099 else {
5100 G.O.fnThumbnailL1DisplayEffect(item.$elt, item, n, delay);
5101 }
5102 }
5103 else {
5104 G.GOM.thumbnails2Display.push({itm: item, d: delay});
5105 // ThumbnailDisplayAnim2(item, delay);
5106 }
5107 return;
5108 }
5109 else {
5110 item.$elt.css({ opacity: 1 });
5111 ThumbnailAppearFinish(item);
5112 }
5113 }
5114 }
5115
5116
5117 // displays thumbnail stacks at the end of the display animation
5118 function ThumbnailAppearFinish( item ) {
5119
5120 // add stacks
5121 var ns = G.tn.opt.Get('stacks');
5122 if( ns > 0 ) {
5123 // display stacks
5124 item.$elt.css({ display: 'block'});
5125 var o = 0.9;
5126 // set stack opacity
5127 for( var i = ns-1; i>=0; i-- ) {
5128 item.$elt.eq(i).css('opacity', o);
5129 o = o - 0.2;
5130 }
5131
5132 }
5133 }
5134
5135
5136 function ThumbnailDisplayAnim2( item, delay ) {
5137 function randomIntFromInterval(min,max) {
5138 return Math.floor(Math.random()*(max-min+1)+min);
5139 }
5140 var oFrom = {};
5141 var oTo = {};
5142
5143 switch (G.tn.opt.Get('displayTransition')) {
5144 case 'RANDOMSCALE':
5145 var scales = [0.95, 1, 1.05, 1.1];
5146 var zi = [1, 2, 3, 4];
5147
5148 var r = randomIntFromInterval(0,3);
5149 while( r == G.GOM.lastRandomValue ) {
5150 r = randomIntFromInterval(0,3);
5151 }
5152 G.GOM.lastRandomValue = r;
5153 var f = scales[r];
5154 // item.$elt.css({ 'z-index': G.GOM.lastZIndex+zi[r], 'box-shadow': '-1px 2px 5px 1px rgba(0, 0, 0, 0.7)' });
5155 item.$elt.css({ 'z-index': G.GOM.lastZIndex+zi[r], 'box-shadow': '0px 0px 5px 3px rgba(0,0,0,0.74)' });
5156
5157 oFrom = { scale: 0.5, opacity:0 };
5158 oTo = { scale: f, opacity:1 };
5159 break;
5160
5161 case 'SCALEUP':
5162 var f = G.tn.opt.Get('displayTransitionStartVal');
5163 if( f == 0 ) { f = 0.6; } // default value
5164 oFrom = { scale: f, opacity: 0 };
5165 oTo = { scale: 1, opacity: 1 };
5166 break;
5167
5168 case 'SCALEDOWN':
5169 var f = G.tn.opt.Get('displayTransitionStartVal');
5170 if( f == 0 ) { f=1.3; } // default value
5171 oFrom = { scale: f, opacity: 0 };
5172 oTo = { scale: 1, opacity: 1 };
5173 break;
5174 case 'SLIDEUP':
5175 var f = G.tn.opt.Get('displayTransitionStartVal');
5176 if( f == 0 ) { f=50; } // default value
5177 oFrom = { opacity: 0, translateY: f };
5178 oTo = { opacity: 1, translateY: 0 };
5179 break;
5180 case 'SLIDEDOWN':
5181 var f=G.tn.opt.Get('displayTransitionStartVal');
5182 if( f == 0 ) { f=-50; } // default value
5183 oFrom = { opacity: 0, translateY: f };
5184 oTo = { opacity: 1, translateY: 0 };
5185 break;
5186 case 'FLIPUP':
5187 var f=G.tn.opt.Get('displayTransitionStartVal');
5188 if( f == 0 ) { f=100; } // default value
5189 oFrom = { opacity: 0, translateY: f, rotateX: 45 };
5190 oTo = { opacity: 1, translateY: 0, rotateX: 0 };
5191 break;
5192
5193 case 'FLIPDOWN':
5194 var f=G.tn.opt.Get('displayTransitionStartVal');
5195 if( f == 0 ) { f=-100; } // default value
5196 oFrom = { opacity: 0, translateY: f, rotateX: -45 };
5197 oTo = { opacity: 1, translateY: 0, rotateX: 0 };
5198 break;
5199 case 'SLIDEUP2':
5200 var f=G.tn.opt.Get('displayTransitionStartVal');
5201 if( f == 0 ) { f=100; } // default value
5202 oFrom = { opacity: 0, translateY: f, rotateY: 40 };
5203 oTo = { opacity: 1, translateY: 0, rotateY: 0 };
5204 break;
5205 case 'SLIDEDOWN2':
5206 var f=G.tn.opt.Get('displayTransitionStartVal');
5207 if( f == 0 ) { f=-100; } // default value
5208 oFrom = { opacity: 0, translateY: f, rotateY: 40 };
5209 oTo = { opacity: 1, translateY: 0, rotateY: 0 };
5210 break;
5211 case 'SLIDERIGHT':
5212 var f=G.tn.opt.Get('displayTransitionStartVal');
5213 if( f == 0 ) { f=-150; } // default value
5214 oFrom = { opacity: 0, translateX: f };
5215 oTo = { opacity: 1, translateX: 0 };
5216 break;
5217
5218 case 'SLIDELEFT':
5219 var f=G.tn.opt.Get('displayTransitionStartVal');
5220 if( f == 0 ) { f=150; } // default value
5221 oFrom = { opacity: 0, translateX: f };
5222 oTo = { opacity: 1, translateX: 0 };
5223 break;
5224
5225 case 'FADEIN':
5226 oFrom = { opacity: 0 };
5227 oTo = { opacity: 1 };
5228 break;
5229
5230
5231 }
5232
5233 var tweenable = new NGTweenable();
5234 tweenable.tween({
5235 from: oFrom,
5236 to: oTo,
5237 attachment: { $e:item.$elt, item: item, tw: tweenable },
5238 delay: delay,
5239 duration: G.tn.opt.Get('displayTransitionDuration'),
5240 easing: G.tn.opt.Get('displayTransitionEasing'),
5241 step: function (state, att) {
5242 window.requestAnimationFrame( function() {
5243 if( att.item.$elt === null ) { // the thumbnail may have been destroyed since the start of the animation
5244 att.tw.stop(false);
5245 return;
5246 }
5247 switch (G.tn.opt.Get('displayTransition')) {
5248 case 'RANDOMSCALE':
5249 att.$e.css( G.CSStransformName , 'scale(' + state.scale + ')').css('opacity', state.opacity);
5250 break;
5251 case 'SCALEUP':
5252 att.$e.css( G.CSStransformName , 'scale('+state.scale+')').css('opacity',state.opacity);
5253 break;
5254 case 'SCALEDOWN':
5255 att.item.$elt.last().css('opacity', state.opacity);
5256 att.item.CSSTransformSet('.nGY2GThumbnail', 'scale', state.scale);
5257 att.item.CSSTransformApply('.nGY2GThumbnail');
5258 break;
5259 case 'SLIDEUP':
5260 att.item.$elt.css('opacity', state.opacity);
5261 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px, '+state.translateY + 'px');
5262 att.item.CSSTransformApply('.nGY2GThumbnail');
5263 break;
5264 case 'SLIDEDOWN':
5265 att.item.$elt.css('opacity', state.opacity);
5266 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px,'+state.translateY+'px');
5267 att.item.CSSTransformApply('.nGY2GThumbnail');
5268 break;
5269 case 'FLIPUP':
5270 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px,'+state.translateY+'px');
5271 att.item.CSSTransformSet('.nGY2GThumbnail', 'rotateX', state.rotateX+'deg');
5272 att.item.$elt.css('opacity', state.opacity);
5273 att.item.CSSTransformApply('.nGY2GThumbnail');
5274 break;
5275 case 'FLIPDOWN':
5276 att.item.$elt.css('opacity', state.opacity);
5277 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px,' + state.translateY + 'px');
5278 att.item.CSSTransformSet('.nGY2GThumbnail', 'rotateX', state.rotateX + 'deg');
5279 att.item.CSSTransformApply('.nGY2GThumbnail');
5280 break;
5281 case 'SLIDEUP2':
5282 att.item.$elt.css('opacity', state.opacity);
5283 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px,' + state.translateY + 'px');
5284 att.item.CSSTransformSet('.nGY2GThumbnail', 'rotateY', state.rotateY + 'deg');
5285 att.item.CSSTransformApply('.nGY2GThumbnail');
5286 break;
5287 case 'SLIDEDOWN2':
5288 att.item.$elt.css('opacity', state.opacity);
5289 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', '0px, ' + state.translateY + 'px');
5290 att.item.CSSTransformSet('.nGY2GThumbnail', 'rotateY', state.rotateY + 'deg');
5291 att.item.CSSTransformApply('.nGY2GThumbnail');
5292 break;
5293 case 'SLIDERIGHT':
5294 att.item.$elt.css('opacity', state.opacity);
5295 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', state.translateX + 'px, 0px');
5296 att.item.CSSTransformApply('.nGY2GThumbnail');
5297 break;
5298 case 'SLIDELEFT':
5299 att.item.CSSTransformSet('.nGY2GThumbnail', 'translate', state.translateX + 'px, 0px');
5300 att.item.$elt.css('opacity', state.opacity);
5301 att.item.CSSTransformApply('.nGY2GThumbnail');
5302 break;
5303 case 'FADEIN':
5304 att.$e.css(state);
5305 break;
5306 }
5307 });
5308 // att.$e.css( G.CSStransformName , 'scale('+state.scale+')').css('opacity',state.opacity);
5309 },
5310 finish: function (state, att) {
5311 window.requestAnimationFrame( function() {
5312 if( att.item.$elt === null ) { return; }
5313
5314 switch (G.tn.opt.Get('displayTransition')) {
5315 case 'RANDOMSCALE':
5316 att.$e.css( G.CSStransformName , 'scale('+state.scale+')').css('opacity', '');
5317 break;
5318 case 'SCALEUP':
5319 att.$e.css( G.CSStransformName , '').css('opacity', '');
5320 break;
5321 case 'SCALEDOWN':
5322 att.item.$elt.last().css('opacity', '');
5323 att.item.CSSTransformSet('.nGY2GThumbnail', 'scale', state.scale);
5324 att.item.CSSTransformApply('.nGY2GThumbnail');
5325 break;
5326 case 'SLIDEUP':
5327 att.item.$elt.css('opacity', '');
5328 break;
5329 case 'SLIDEDOWN':
5330 att.item.$elt.css('opacity', '');
5331 break;
5332 case 'FLIPUP':
5333 att.item.$elt.css('opacity', '');
5334 break;
5335 case 'FLIPDOWN':
5336 att.item.$elt.css('opacity', '');
5337 break;
5338 case 'SLIDEUP2':
5339 att.item.$elt.css('opacity', '');
5340 break;
5341 case 'SLIDEDOWN2':
5342 att.item.$elt.css('opacity', '');
5343 att.item.CSSTransformApply('.nGY2GThumbnail');
5344 break;
5345 case 'SLIDERIGHT':
5346 att.item.$elt.css('opacity', '');
5347 break;
5348 case 'SLIDELEFT':
5349 att.item.$elt.css('opacity', '');
5350 break;
5351 case 'FADEIN':
5352 att.$e.css('opacity', '');
5353 break;
5354 }
5355 ThumbnailAppearFinish(att.item);
5356 });
5357
5358 }
5359 });
5360
5361 }
5362
5363 // batch display thumbnails with animation
5364 function ThumbnailDisplayAnimBatch() {
5365
5366 G.GOM.thumbnails2Display.forEach( function(one) {
5367 ThumbnailDisplayAnim2(one.itm, one.d);
5368 });
5369 G.GOM.thumbnails2Display=[];
5370 }
5371
5372
5373
5374 // ######################################
5375 // Gallery display animation
5376 function GalleryAppear() {
5377
5378 var d=G.galleryDisplayTransitionDuration.Get();
5379 switch( G.galleryDisplayTransition.Get() ){
5380 case 'ROTATEX':
5381 G.$E.base.css({ perspective: '1000px', 'perspective-origin': '50% 0%' });
5382 var tweenable = new NGTweenable();
5383 tweenable.tween({
5384 from: { r: 50 },
5385 to: { r: 0 },
5386 attachment: { orgIdx: G.GOM.albumIdx },
5387 duration: d,
5388 easing: 'easeOutCirc',
5389 step: function (state, att) {
5390 if( att.orgIdx == G.GOM.albumIdx ) {
5391 // window.ng_draf( function() {
5392 G.$E.conTnParent.css( G.CSStransformName , 'rotateX(' + state.r + 'deg)');
5393 // });
5394 }
5395 }
5396 });
5397 break;
5398 case 'SLIDEUP':
5399 G.$E.conTnParent.css({ opacity: 0 });
5400 var tweenable = new NGTweenable();
5401 tweenable.tween({
5402 from: { y: 200, o: 0 },
5403 to: { y: 0, o: 1 },
5404 attachment: { orgIdx: G.GOM.albumIdx },
5405 duration: d,
5406 easing: 'easeOutCirc',
5407 step: function (state, att) {
5408 if( att.orgIdx == G.GOM.albumIdx ) {
5409 // window.ng_draf( function() {
5410 G.$E.conTnParent.css( G.CSStransformName , 'translate( 0px, '+state.y + 'px)').css('opacity', state.o);
5411 // });
5412 }
5413 }
5414 });
5415 break;
5416 case 'NONE':
5417 default:
5418 break;
5419 }
5420
5421
5422 }
5423
5424 // ######################################
5425 // ##### THUMBNAIL HOVER MANAGEMENT #####
5426 // ######################################
5427
5428 function ThumbnailOverInit( GOMidx ) {
5429 // Over init in 2 step:
5430 // 1) init with thumbnailBuildInit2 parameter
5431 // 2) init with the hover effect parameter
5432
5433
5434 var curTn = G.GOM.items[GOMidx];
5435 var item = G.I[curTn.thumbnailIdx];
5436
5437 if( item.$elt == null ) { return; } // zombie
5438
5439 var fu = G.O.fnThumbnailHoverInit;
5440 if( fu !== null ) {
5441 typeof fu == 'function' ? fu($e, item, GOMidx) : window[fu]($e, item, GOMidx);
5442 }
5443
5444 // build initialization
5445 var inits = G.tn.buildInit.get();
5446 for( var j = 0; j < inits.length; j++) {
5447 switch( inits[j].property ) {
5448 // CSS Transform
5449 case 'scale':
5450 case 'rotateX':
5451 case 'rotateY':
5452 case 'rotateZ':
5453 case 'translateX':
5454 case 'translateY':
5455 case 'translateZ':
5456 item.CSSTransformSet(inits[j].element, inits[j].property, inits[j].value);
5457 item.CSSTransformApply(inits[j].element);
5458 break;
5459 // CSS filter
5460 case 'blur':
5461 case 'brightness':
5462 case 'grayscale':
5463 case 'sepia':
5464 case 'contrast':
5465 case 'opacity':
5466 case 'saturate':
5467 item.CSSFilterSet(inits[j].element, inits[j].property, inits[j].value);
5468 item.CSSFilterApply(inits[j].element);
5469 break;
5470 default:
5471 var $t=item.$getElt(inits[j].element);
5472 $t.css( inits[j].property, inits[j].value );
5473 break;
5474 }
5475 }
5476
5477 // hover
5478 var effects = G.tn.hoverEffects.get();
5479 for( var j = 0; j < effects.length; j++) {
5480 if( effects[j].firstKeyframe === true ) {
5481 switch( effects[j].type ) {
5482 case 'scale':
5483 case 'rotateX':
5484 case 'rotateY':
5485 case 'rotateZ':
5486 case 'translateX':
5487 case 'translateY':
5488 case 'translateZ':
5489 item.CSSTransformSet(effects[j].element, effects[j].type, effects[j].from);
5490 item.CSSTransformApply(effects[j].element);
5491 break;
5492 case 'blur':
5493 case 'brightness':
5494 case 'grayscale':
5495 case 'sepia':
5496 case 'contrast':
5497 case 'opacity':
5498 case 'saturate':
5499 item.CSSFilterSet(effects[j].element, effects[j].type, effects[j].from);
5500 item.CSSFilterApply(effects[j].element);
5501 break;
5502 default:
5503 var $t = item.$getElt(effects[j].element);
5504 $t.css( effects[j].type, effects[j].from );
5505 break;
5506
5507 }
5508 }
5509 }
5510 item.hoverInitDone=true;
5511 }
5512
5513 function ThumbnailHoverReInitAll() {
5514 if( G.GOM.albumIdx == -1 ) { return; };
5515 var l = G.GOM.items.length;
5516 for( var i = 0; i < l ; i++ ) {
5517 ThumbnailOverInit(i);
5518 // G.GOM.items[i].hovered=false;
5519 G.I[G.GOM.items[i].thumbnailIdx].hovered = false;
5520 }
5521 }
5522
5523
5524 function ThumbnailHover( GOMidx ) {
5525 if( G.GOM.albumIdx == -1 || !G.galleryResizeEventEnabled ) { return; };
5526 if( G.GOM.slider.hostIdx == GOMidx ) {
5527 // slider hosted on thumbnail -> no hover effect
5528 return;
5529 }
5530 var curTn = G.GOM.items[GOMidx];
5531 var item = G.I[curTn.thumbnailIdx];
5532 if( item.kind == 'albumUp' || item.$elt == null ) { return; }
5533
5534 item.hovered = true;
5535
5536 var fu = G.O.fnThumbnailHover;
5537 if( fu !== null ) {
5538 typeof fu == 'function' ? fu(item.$elt, item, GOMidx) : window[fu](item.$elt, item, GOMidx);
5539 }
5540 var effects = G.tn.hoverEffects.get();
5541
5542 try {
5543 for( var j = 0; j < effects.length; j++) {
5544 if( effects[j].hoverin === true ) {
5545 //item.animate( effects[j], j*10, true );
5546 item.animate( effects[j], 0, true );
5547 }
5548 }
5549 // effects on whole layout
5550 // GalleryResize( GOMidx );
5551 }
5552 catch (e) {
5553 NanoAlert(G, 'error on hover: ' + e.message );
5554 }
5555
5556 }
5557
5558 function ThumbnailHoverOutAll() {
5559 if( G.GOM.albumIdx == -1 ) { return; };
5560 var l = G.GOM.items.length;
5561 for( var i = 0; i < l ; i++ ) {
5562 if( G.GOM.items[i].inDisplayArea ) {
5563 ThumbnailHoverOut(i);
5564 }
5565 else {
5566 G.I[G.GOM.items[i].thumbnailIdx].hovered = false;
5567 }
5568 }
5569 }
5570
5571
5572 function ThumbnailHoverOut( GOMidx ) {
5573 if( G.GOM.albumIdx == -1 || !G.galleryResizeEventEnabled ) { return; }
5574
5575 if( G.GOM.slider.hostIdx == GOMidx ) {
5576 // slider on thumbnail -> no hover effect
5577 return;
5578 }
5579
5580 var curTn = G.GOM.items[GOMidx];
5581 var item = G.I[curTn.thumbnailIdx];
5582 if( item.kind == 'albumUp' || !item.hovered ) { return; }
5583 item.hovered = false;
5584 if( item.$elt == null ) { return; }
5585
5586 var fu = G.O.fnThumbnailHoverOut;
5587 if( fu !== null ) {
5588 typeof fu == 'function' ? fu(item.$elt, item, GOMidx) : window[fu](item.$elt, item, GOMidx);
5589 }
5590
5591 var effects = G.tn.hoverEffects.get();
5592 try {
5593 for( var j = 0; j < effects.length; j++) {
5594 if( effects[j].hoverout === true ) {
5595 // item.animate( effects[j], j*10, false );
5596 item.animate( effects[j], 0, false );
5597 }
5598 }
5599 // effects on whole layout
5600 // GalleryResize( );
5601 }
5602 catch (e) {
5603 NanoAlert(G, 'error on hoverOut: ' + e.message );
5604 }
5605
5606 }
5607
5608
5609 /** @function DisplayPhoto */
5610 function DisplayPhoto( imageID, albumID ) {
5611
5612 if( G.O.debugMode ) { console.log('#DisplayPhoto : '+ albumID +'-'+ imageID); }
5613 var albumIdx = NGY2Item.GetIdx(G, albumID);
5614 if( albumIdx == 0 ) {
5615 G.GOM.curNavLevel = 'l1';
5616 }
5617 else {
5618 G.GOM.curNavLevel = 'lN';
5619 }
5620
5621 if( albumIdx == -1 ) {
5622 // get content of album on root level
5623 if( G.O.kind != '' ) {
5624 // do not add album if Markup or Javascript data
5625 NGY2Item.New( G, '', '', albumID, '0', 'album' ); // create empty album
5626 albumIdx = G.I.length - 1;
5627 }
5628 }
5629
5630 var ngy2ItemIdx = NGY2Item.GetIdx(G, imageID);
5631 if( ngy2ItemIdx == -1 ) {
5632 // get content of the album
5633 AlbumGetContent( albumID, DisplayPhoto, imageID, albumID );
5634 return;
5635 }
5636
5637 if( G.O.debugMode ) { console.log('#DisplayPhoto : '+ ngy2ItemIdx); }
5638
5639 DisplayPhotoIdx(ngy2ItemIdx);
5640
5641 }
5642
5643
5644 // BETA -> NOT finished and not used at this time
5645 // Retrieve the title+description of ONE album
5646 function albumGetInfo( albumIdx, fnToCall ) {
5647 var url = '';
5648 var kind = 'image';
5649
5650 switch( G.O.kind ) {
5651 case 'json':
5652 // TODO
5653 case 'flickr':
5654 // TODO
5655 case 'picasa':
5656 case 'google':
5657 case 'google2':
5658 default:
5659 url = G.Google.url() + 'user/'+G.O.userID+'/albumid/'+G.I[albumIdx].GetID()+'?alt=json&&max-results=1&fields=title';
5660 break;
5661 }
5662
5663 jQuery.ajaxSetup({ cache: false });
5664 jQuery.support.cors = true;
5665
5666 var tId = setTimeout( function() {
5667 // workaround to handle JSONP (cross-domain) errors
5668 //PreloaderHide();
5669 NanoAlert(G, 'Could not retrieve AJAX data...');
5670 }, 60000 );
5671 jQuery.getJSON(url, function(data, status, xhr) {
5672 clearTimeout(tId);
5673 //PreloaderHide();
5674
5675 fnToCall( G.I[albumIdx].GetID() );
5676
5677 })
5678 .fail( function(jqxhr, textStatus, error) {
5679 clearTimeout(tId);
5680 //PreloaderHide();
5681 var err = textStatus + ', ' + error;
5682 NanoAlert('Could not retrieve ajax data: ' + err);
5683 });
5684
5685 }
5686
5687
5688 // function AlbumGetContent( albumIdx, fnToCall ) {
5689 function AlbumGetContent( albumID, fnToCall, fnParam1, fnParam2 ) {
5690 // var url='';
5691 // var kind='image';
5692 // var albumIdx=NGY2Item.GetIdx(G, albumID);
5693 // var photoIdx=NGY2Item.GetIdx(G, photoID);
5694
5695 switch( G.O.kind ) {
5696 // MARKUP / API
5697 case '':
5698 AlbumGetMarkupOrApi(fnToCall, fnParam1, fnParam2);
5699 break;
5700 // JSON, Flickr, Picasa, ...
5701 default:
5702 jQuery.nanogallery2['data_'+G.O.kind](G, 'AlbumGetContent', albumID, fnToCall, fnParam1, fnParam2 );
5703 }
5704
5705 }
5706
5707 var mediaList = {
5708 youtube : {
5709 getID: function( url ) {
5710 // https://stackoverflow.com/questions/10591547/how-to-get-youtube-video-id-from-url
5711 var s = url.match( /(?:https?:\/{2})?(?:w{3}\.)?youtu(?:be)?\.(?:com|be)(?:\/watch\?v=|\/)([^\s&]+)/ );
5712 return s != null ? s[1] : null;
5713 },
5714 thumbUrl: function( id ) {
5715 return 'https://img.youtube.com/vi/' + id + '/hqdefault.jpg';
5716 },
5717 url: function( id ) {
5718 return 'https://www.youtube.com/embed/' + id;
5719 },
5720 markup: function( id ) {
5721 // return '<iframe class="nGY2ViewerMedia" src="https://www.youtube.com/embed/' + id + '?rel=0" frameborder="0" gesture="media" allowfullscreen></iframe>';
5722 return '<iframe class="nGY2ViewerMedia" src="https://www.youtube.com/embed/' + id + '?rel=0" frameborder="0" allow="autoplay" allowfullscreen></iframe>';
5723 },
5724 kind: 'iframe'
5725 },
5726 vimeo : {
5727 getID: function( url ) {
5728 // https://stackoverflow.com/questions/2916544/parsing-a-vimeo-id-using-javascript
5729 var s = url.match( /^.*(vimeo\.com\/)((channels\/[A-z]+\/)|(groups\/[A-z]+\/videos\/))?([0-9]+)/ );
5730 return s != null ? s[5] : null;
5731 },
5732 url: function( id ) {
5733 return 'https://player.vimeo.com/video/' + id;
5734 },
5735 markup: function( id ) {
5736 // return '<iframe class="nGY2ViewerMedia" src="https://player.vimeo.com/video/' + id + '?rel=0" frameborder="0" gesture="media" allowfullscreen></iframe>';
5737 return '<iframe class="nGY2ViewerMedia" src="https://player.vimeo.com/video/' + id + '?rel=0" frameborder="0" allow="autoplay" allowfullscreen></iframe>';
5738 },
5739 kind: 'iframe'
5740 },
5741 dailymotion : {
5742 getID: function( url ) {
5743 // https://stackoverflow.com/questions/12387389/how-to-parse-dailymotion-video-url-in-javascript
5744 var m = url.match(/^.+dailymotion.com\/(video|hub)\/([^_]+)[^#]*(#video=([^_&]+))?/);
5745 if (m !== null) {
5746 if(m[4] !== undefined) {
5747 return m[4];
5748 }
5749 return m[2];
5750 }
5751 return null;
5752 },
5753 thumbUrl: function( id ) {
5754 return 'https://www.dailymotion.com/thumbnail/video/' + id;
5755 },
5756 url: function( id ) {
5757 return 'https://www.dailymotion.com/embed/video/' + id;
5758 },
5759 markup: function( id ) {
5760 // return '<iframe class="nGY2ViewerMedia" src="https://www.dailymotion.com/embed/video/' + id + '?rel=0" frameborder="0" gesture="media" allowfullscreen></iframe>';
5761 return '<iframe class="nGY2ViewerMedia" src="https://www.dailymotion.com/embed/video/' + id + '?rel=0" frameborder="0" allow="autoplay" allowfullscreen></iframe>';
5762 },
5763 kind: 'iframe'
5764 },
5765 selfhosted : {
5766 // SELF-HOSTED VIDEOS
5767 getID: function( url ) {
5768 // In order to leave things as is, I used ID to identify the extension
5769 // https://stackoverflow.com/questions/6997262/how-to-pull-url-file-extension-out-of-url-string-using-javascript
5770 // Make sure the method used for verifying the extension matches the kind of url your selfhosted video has
5771 var extension = url.split('.').pop();
5772
5773 // supported extensions
5774 var s = ( extension === 'mp4' || extension === 'webm' || extension === 'ogv' || extension === '3gp' ) ? extension : null ;
5775 return s;
5776 },
5777 markup: function( url ) {
5778 // return '<video controls class="nGY2ViewerMedia"><source src="${id.src}" type="video/${id.type}" preload="auto">Your browser does not support the video tag (HTML 5).</video>';
5779 var extension = url.split('.').pop();
5780 return '<video controls class="nGY2ViewerMedia"><source src="'+ url +'" type="video/'+ extension +'" preload="auto">Your browser does not support the video tag (HTML 5).</video>';
5781 },
5782 kind: 'video',
5783 selfhosted : true
5784 }
5785 };
5786
5787 function AlbumGetMarkupOrApi ( fnToCall, fnParam1, fnParam2 ) {
5788
5789 if( G.markupOrApiProcessed === true ) {
5790 // already processed (maybe location hash to unknow reference) -> display root album
5791 DisplayAlbum('-1', 0);
5792 return;
5793 }
5794
5795 if( G.O.items !== undefined && G.O.items !== null ) {
5796 // data defined as an object in an option parameter
5797 GetContentApiObject();
5798 }
5799 else {
5800 if( G.O.$markup.length > 0 ) {
5801 // data defined as markup (href elements)
5802 GetContentMarkup( G.O.$markup );
5803 G.O.$markup=[] ;
5804 }
5805 else {
5806 NanoConsoleLog(G, 'error: no media to process.');
5807 return;
5808 }
5809 }
5810
5811 G.markupOrApiProcessed = true;
5812 if( fnToCall !== null && fnToCall !== undefined) {
5813 fnToCall( fnParam1, fnParam2, null );
5814 }
5815 }
5816
5817 function StartsWithProtocol ( path ) {
5818 if( path == null || path == undefined ) { return false; }
5819
5820 var pattern = /^((http|https|ftp|ftps|file):\/\/)/;
5821 if( !pattern.test(path) ) {
5822 // not a full URL
5823 return false;
5824 }
5825 return true;
5826 }
5827
5828 function GetContentApiObject() {
5829 var foundAlbumID=false;
5830 var nbTitles = 0;
5831 var AlbumPostProcess = NGY2Tools.AlbumPostProcess.bind(G);
5832
5833 G.I[0].contentIsLoaded=true;
5834
5835 jQuery.each(G.O.items, function(i,item){
5836
5837 var title = '';
5838 title=GetI18nItem(item, 'title');
5839 if( title === undefined ) { title=''; }
5840
5841 var src='';
5842 if( item['src'+RetrieveCurWidth().toUpperCase()] !== undefined ) {
5843 src = item['src'+RetrieveCurWidth().toUpperCase()];
5844 }
5845 else {
5846 src = item.src;
5847 }
5848 if( !StartsWithProtocol(src) ) {
5849 src = G.O.itemsBaseURL + src;
5850 }
5851
5852 var thumbsrc = '';
5853 if( item.srct !== undefined && item.srct.length > 0 ) {
5854 thumbsrc = item.srct;
5855 if( !StartsWithProtocol(thumbsrc) ) {
5856 thumbsrc = G.O.itemsBaseURL + thumbsrc;
5857 }
5858 }
5859 else {
5860 thumbsrc = src;
5861 }
5862
5863 var thumbsrcX2 = '';
5864 if( item.srct2x !== undefined && item.srct2x.length > 0 ) {
5865 thumbsrcX2 = item.srct2x;
5866 if( !StartsWithProtocol(thumbsrcX2) ) {
5867 thumbsrcX2 = G.O.itemsBaseURL + thumbsrcX2;
5868 }
5869 }
5870 else {
5871 if( thumbsrc != '' ) {
5872 thumbsrcX2 = thumbsrc;
5873 }
5874 else {
5875 thumbsrcX2 = src;
5876 }
5877 }
5878
5879 if( G.O.thumbnailLabel.get('title') != '' ) {
5880 title = GetImageTitle(src);
5881 }
5882
5883 var description=''; //'&nbsp;';
5884 description=GetI18nItem(item,'description');
5885 if( description === undefined ) { description=''; }
5886 //if( toType(item.description) == 'string' ) {
5887 // description=item.description;
5888 //}
5889
5890 var tags = GetI18nItem(item, 'tags');
5891 if( tags === undefined ) { tags=''; }
5892
5893 var albumID = 0;
5894 if( item.albumID !== undefined ) {
5895 albumID=item.albumID;
5896 foundAlbumID = true;
5897 }
5898 var ID = null;
5899 if( item.ID !== undefined ) {
5900 ID = item.ID;
5901 }
5902 var kind = 'image';
5903 if( item.kind !== undefined && item.kind.length > 0 ) {
5904 kind = item.kind;
5905 }
5906
5907 var newItem=NGY2Item.New( G, title, description, ID, albumID, kind, tags );
5908 if( title != '' ) {
5909 nbTitles++;
5910 }
5911
5912 // media source url - img is the default media kind
5913 newItem.setMediaURL( src, 'img');
5914
5915 // manage media kinds other than IMG
5916 jQuery.each(mediaList, function ( n, media ) {
5917 var id = media.getID(src);
5918 if( id != null ) {
5919 if( typeof media.url == 'function' ) { src = media.url(id); }
5920 if( typeof media.thumbUrl == 'function' ) { thumbsrc = media.thumbUrl(id); }
5921 newItem.mediaKind = media.kind;
5922 newItem.mediaMarkup = ( media.selfhosted ) ? media.markup( src ) : media.markup(id);
5923 return false;
5924 }
5925 });
5926
5927 // image size
5928 if( item.imageWidth !== undefined ) { newItem.imageWidth = item.width; }
5929 if( item.imageHeight !== undefined ) { newItem.imageHeight = item.height; }
5930
5931 // THUMBNAILS
5932
5933 // thumbnail image size
5934 var tw = item.imgtWidth !== undefined ? item.imgtWidth : 0;
5935 var th = item.imgtHeight !== undefined ? item.imgtHeight : 0;
5936
5937 // default thumbnail URL and size
5938 newItem.thumbs = {
5939 url: { l1 : { xs: thumbsrc, sm: thumbsrc, me: thumbsrc, la: thumbsrc, xl: thumbsrc }, lN : { xs: thumbsrc, sm: thumbsrc, me: thumbsrc, la: thumbsrc, xl: thumbsrc } },
5940 width: { l1 : { xs: tw, sm: tw, me: tw, la: tw, xl: tw }, lN : { xs: tw, sm: tw, me: tw, la: tw, xl: tw } },
5941 height: { l1 : { xs: th, sm: th, me: th, la: th, xl: th }, lN : { xs: th, sm: th, me: th, la: th, xl: th } }
5942 };
5943
5944 // default media type -> IMG
5945 if( newItem.mediaKind == 'img' ) {
5946
5947 // responsive thumbnails URL and size
5948 var lst=['xs', 'sm', 'me', 'la', 'xl'];
5949 for( var i=0; i< lst.length; i++ ) {
5950 // url
5951 var turl = item['srct' + lst[i].toUpperCase()];
5952 if( turl !== undefined ) {
5953 if( !StartsWithProtocol(turl) ) {
5954 turl = G.O.itemsBaseURL + turl;
5955 }
5956 newItem.url.l1[lst[i]] = turl;
5957 newItem.url.lN[lst[i]] = turl;
5958 }
5959 // width
5960 var tw = item['imgt' + lst[i].toUpperCase() + 'Width'];
5961 if( tw != undefined ) {
5962 newItem.width.l1[lst[i]] = parseInt(tw);
5963 newItem.width.lN[lst[i]] = parseInt(tw);
5964 }
5965 // height
5966 var th = item['imgt' + lst[i].toUpperCase() + 'Height'];
5967 if( th != undefined ) {
5968 newItem.height.l1[lst[i]] = parseInt(th);
5969 newItem.height.lN[lst[i]] = parseInt(th);
5970 }
5971 }
5972 }
5973
5974 // dominant colors (needs to be a base64 gif)
5975 if( item.imageDominantColors !== undefined ) {
5976 newItem.imageDominantColors = item.imageDominantColors;
5977 }
5978 // dominant color (rgb hex)
5979 if( item.imageDominantColor !== undefined ) {
5980 newItem.imageDominantColor = item.imageDominantColor;
5981 }
5982
5983 // dest url
5984 if( item.destURL !== undefined && item.destURL.length>0 ) {
5985 newItem.destinationURL = item.destURL;
5986 }
5987
5988 // download image url
5989 if( item.downloadURL !== undefined && item.downloadURL.length>0 ) {
5990 newItem.downloadURL = item.downloadURL;
5991 }
5992
5993 // EXIF DATA
5994 // Exif - model
5995 if( item.exifModel !== undefined ) { newItem.exif.model = item.exifModel; }
5996 // Exif - flash
5997 if( item.exifFlash !== undefined ) { newItem.exif.flash = item.exifFlash; }
5998 // Exif - focallength
5999 if( item.exifFocalLength !== undefined ) { newItem.exif.focallength = item.exifFocalLength; }
6000 // Exif - fstop
6001 if( item.exifFStop !== undefined ) { newItem.exif.fstop = item.exifFStop; }
6002 // Exif - exposure
6003 if( item.exifExposure !== undefined ) { newItem.exif.exposure = item.exifExposure; }
6004 // Exif - time
6005 if( item.exifIso !== undefined ) { newItem.exif.iso = item.exifIso; }
6006 // Exif - iso
6007 if( item.exifTime !== undefined ) { newItem.exif.time = item.exifTime; }
6008 // Exif - location
6009 if( item.exifLocation !== undefined ) { newItem.exif.location = item.exifLocation; }
6010
6011
6012 // custom data
6013 if( item.customData !== null ) {
6014 newItem.customData = cloneJSObject( item.customData );
6015 }
6016
6017 newItem.contentIsLoaded = true;
6018
6019 var fu = G.O.fnProcessData;
6020 if( fu !== null ) {
6021 typeof fu == 'function' ? fu(newItem, 'api', item) : window[fu](newItem, 'api', item);
6022 }
6023
6024 AlbumPostProcess(albumID);
6025 });
6026
6027 // if( foundAlbumID ) { G.O.displayBreadcrumb=true; }
6028 if( nbTitles == 0 ) { G.O.thumbnailLabel.display=false; }
6029
6030 }
6031
6032
6033 function GetContentMarkup( $elements ) {
6034 var foundAlbumID = false;
6035 var nbTitles = 0;
6036 var AlbumPostProcess = NGY2Tools.AlbumPostProcess.bind(G);
6037
6038 G.I[0].contentIsLoaded = true;
6039
6040 jQuery.each($elements, function(i, item){
6041
6042 // create dictionnary with all data attribute name in lowercase (to be case unsensitive)
6043 var data = {
6044 // some default values
6045 'data-ngdesc': '', // item description
6046 'data-ngid': null, // ID
6047 'data-ngkind': 'image', // kind (image, album, albumup)
6048 'data-ngtags': null, // tags
6049 'data-ngdest': '', // destination URL
6050 'data-ngthumbimgwidth': 0, // thumbnail width
6051 'data-ngthumbimgheight': 0, // thumbnail height
6052 'data-ngimagewidth': 0, // image width
6053 'data-ngimageheight': 0, // image height
6054 'data-ngimagedominantcolors': null, // image dominant colors
6055 'data-ngimagedominantcolor': null, // image dominant colors
6056 'data-ngexifmodel': '', // EXIF data
6057 'data-ngexifflash': '',
6058 'data-ngexiffocallength': '',
6059 'data-ngexiffstop': '',
6060 'data-ngexifexposure': '',
6061 'data-ngexifiso': '',
6062 'data-ngexiftime': '',
6063 'data-ngexiflocation': ''
6064 };
6065 [].forEach.call( item.attributes, function(attr) {
6066 data[attr.name.toLowerCase()] = attr.value;
6067 });
6068
6069 // responsive image source
6070 var src = '',
6071 st = RetrieveCurWidth().toUpperCase();
6072 if( data.hasOwnProperty('data-ngsrc'+st) ) {
6073 src = data['data-ngsrc'+st];
6074 }
6075 if( src == '' ) {
6076 src = data['href'];
6077 }
6078 if( !StartsWithProtocol(src) ) {
6079 src = G.O.itemsBaseURL + src;
6080 }
6081
6082 // thumbnail
6083 var thumbsrc = '';
6084 if( data.hasOwnProperty('data-ngthumb') ) {
6085 thumbsrc = data['data-ngthumb'];
6086 if( !StartsWithProtocol(thumbsrc) ) {
6087 thumbsrc = G.O.itemsBaseURL + thumbsrc;
6088 }
6089 }
6090 else {
6091 thumbsrc = src;
6092 }
6093 var thumbsrcX2 = '';
6094 if( data.hasOwnProperty('data-ngthumb2x') ) {
6095 thumbsrcX2 = data['data-ngthumb2x'];
6096 if( !StartsWithProtocol(thumbsrcX2) ) {
6097 thumbsrcX2 = G.O.itemsBaseURL + thumbsrcX2;
6098 }
6099 }
6100
6101 //newObj.description=jQuery(item).attr('data-ngdesc');
6102 var description = data['data-ngdesc'];
6103 var ID = data['id'];
6104 if( ID == undefined ) {
6105 ID = data['data-ngid'];
6106 }
6107 var kind = data['data-ngkind'];
6108 var tags = data['data-ngtags'];
6109
6110 var albumID = '0';
6111 if( data.hasOwnProperty('data-ngalbumid') ) {
6112 albumID = data['data-ngalbumid'];
6113 foundAlbumID = true;
6114 }
6115
6116 var title = jQuery(item).text();
6117 if( !(G.O.thumbnailLabel.get('title') == '' || G.O.thumbnailLabel.get('title') == undefined) ) {
6118 title = GetImageTitle(src);
6119 }
6120
6121
6122 var newItem = NGY2Item.New( G, title, description, ID, albumID, kind, tags );
6123 if( title != '' ) {
6124 nbTitles++;
6125 }
6126
6127 // media source url - img is the default media kind
6128 newItem.setMediaURL( src, 'img');
6129
6130 // manage media kinds other than IMG
6131 newItem.mediaKind = 'img';
6132 jQuery.each(mediaList, function ( n, media ) {
6133 var id = media.getID(src);
6134 if( id != null ) {
6135 if( typeof media.url == 'function' ) { src = media.url(id); }
6136 if( typeof media.thumbUrl == 'function' ) { thumbsrc = media.thumbUrl(id); }
6137 newItem.mediaKind = media.kind;
6138 newItem.mediaMarkup = ( media.selfhosted ) ? media.markup( src ) : media.markup(id);
6139 return false;
6140 }
6141 });
6142
6143
6144 // image size
6145 newItem.imageWidth = parseInt( data['data-ngimagewidth'] );
6146 newItem.imageHeight = parseInt( data['data-ngimageheight'] );
6147
6148 // default thumbnail image URL and size
6149 var tw = parseInt(data['data-ngthumbimgwidth']);
6150 var th = parseInt(data['data-ngthumbimgheight']);
6151 newItem.thumbs = {
6152 url: { l1 : { xs: thumbsrc, sm: thumbsrc, me: thumbsrc, la: thumbsrc, xl: thumbsrc }, lN : { xs: thumbsrc, sm: thumbsrc, me: thumbsrc, la: thumbsrc, xl: thumbsrc } },
6153 width: { l1 : { xs: tw, sm: tw, me: tw, la: tw, xl: tw }, lN : { xs: tw, sm: tw, me: tw, la: tw, xl: tw } },
6154 height: { l1 : { xs: th, sm: th, me: th, la: th, xl: th }, lN : { xs: th, sm: th, me: th, la: th, xl: th } }
6155 };
6156
6157 // default media type -> IMG
6158 if( newItem.mediaKind == 'img' ) {
6159
6160 // responsive thumbnails URL and size
6161 var lst = ['xs', 'sm', 'me', 'la', 'xl'];
6162 for( var i = 0; i < lst.length; i++ ) {
6163 // url
6164 if( data.hasOwnProperty('data-ngthumb' + lst[i]) ) {
6165 var turl=data['data-ngthumb' + lst[i]];
6166 if( !StartsWithProtocol(turl) ) {
6167 turl = G.O.itemsBaseURL + turl;
6168 }
6169 newItem.url.l1[lst[i]] = turl;
6170 newItem.url.lN[lst[i]] = turl;
6171 }
6172
6173 // width
6174 if( data.hasOwnProperty('data-ngthumb' + lst[i] + 'width') ) {
6175 var tw=parseInt(data['data-ngthumb' + lst[i] + 'width']);
6176 newItem.width.l1[lst[i]] = tw;
6177 newItem.width.lN[lst[i]] = tw;
6178 }
6179 // height
6180 if( data.hasOwnProperty('data-ngthumb' + lst[i] + 'height') ) {
6181 var th=parseInt('data-ngthumb' + lst[i] + 'height');
6182 newItem.height.l1[lst[i]] = th;
6183 newItem.height.lN[lst[i]] = th;
6184 }
6185 }
6186 }
6187
6188
6189 // dominant colorS (needs to be a base64 gif)
6190 newItem.imageDominantColors = data['data-ngimagedominantcolors'];
6191 // dominant color (rgb hex)
6192 newItem.imageDominantColor = data['data-ngimagedominantcolors'];
6193
6194 newItem.destinationURL = data['data-ngdest'];
6195 newItem.downloadURL = data['data-ngdownloadurl'];
6196
6197 // Exif - model
6198 newItem.exif.model=data['data-ngexifmodel'];
6199 // Exif - flash
6200 newItem.exif.flash=data['data-ngexifflash'];
6201 // Exif - focallength
6202 newItem.exif.focallength=data['data-ngexiffocallength'];
6203 // Exif - fstop
6204 newItem.exif.fstop=data['data-ngexiffstop'];
6205 // Exif - exposure
6206 newItem.exif.exposure=data['data-ngexifexposure'];
6207 // Exif - iso
6208 newItem.exif.iso=data['data-ngexifiso'];
6209 // Exif - time
6210 newItem.exif.time=data['data-ngexiftime'];
6211 // Exif - location
6212 newItem.exif.location=data['data-ngexiflocation'];
6213
6214 newItem.contentIsLoaded=true;
6215
6216 // custom data
6217 if( jQuery(item).data('customdata') !== undefined ) {
6218 newItem.customData=cloneJSObject(jQuery(item).data('customdata'));
6219 }
6220 // custom data
6221 if( jQuery(item).data('ngcustomdata') !== undefined ) {
6222 newItem.customData=cloneJSObject(jQuery(item).data('ngcustomdata'));
6223 }
6224
6225 var fu=G.O.fnProcessData;
6226 if( fu !== null ) {
6227 typeof fu == 'function' ? fu(newItem, 'markup', item) : window[fu](newItem, 'markup', item);
6228 }
6229
6230 AlbumPostProcess(albumID);
6231
6232 });
6233
6234 // if( foundAlbumID ) { G.O.displayBreadcrumb=true; }
6235 if( nbTitles == 0 ) { G.O.thumbnailLabel.display = false; }
6236
6237 }
6238
6239
6240 // ################################
6241 // ##### DEFINE VARIABLES #####
6242 // ################################
6243
6244
6245 /** @function DefineVariables */
6246 function DefineVariables() {
6247
6248 // change 'picasa' to 'google' for compatibility reason
6249 if( G.O.kind.toUpperCase() == 'PICASA' || G.O.kind.toUpperCase() == 'GOOGLE') {
6250 G.O.kind='google2';
6251 }
6252
6253 // management of screen width
6254 G.GOM.cache.viewport = getViewport();
6255 G.GOM.curWidth = RetrieveCurWidth();
6256
6257 // tumbnail toolbar
6258 jQuery.extend(true, G.tn.toolbar.image, G.O.thumbnailToolbarImage );
6259 jQuery.extend(true, G.tn.toolbar.album, G.O.thumbnailToolbarAlbum );
6260 var t = ['image', 'album'];
6261 var pos= ['topLeft', 'topRight', 'bottomLeft', 'bottomRight']
6262 for( var i=0; i < t.length ; i++ ) {
6263 for( var j=0; j < pos.length ; j++ ) {
6264 G.tn.toolbar[t[i]][pos[j]] = G.tn.toolbar[t[i]][pos[j]].toUpperCase();
6265 }
6266 }
6267
6268 // thumbnails label - level dependant settings
6269 G.O.thumbnailLabel.get = function( opt ) {
6270 if( G.GOM.curNavLevel == 'l1' && G.O.thumbnailL1Label !== undefined && G.O.thumbnailL1Label[opt] !== undefined ) {
6271 return G.O.thumbnailL1Label[opt];
6272 }
6273 else {
6274 return G.O.thumbnailLabel[opt];
6275 }
6276 };
6277 G.O.thumbnailLabel.set = function( opt, value ) {
6278 if( G.GOM.curNavLevel == 'l1' && G.O.thumbnailL1Label !== undefined && G.O.thumbnailL1Label[opt] !== undefined ) {
6279 G.O.thumbnailL1Label[opt]=value;
6280 }
6281 else {
6282 G.O.thumbnailLabel[opt]=value;
6283 }
6284 };
6285
6286 if( G.O.blackList != '' ) { G.blackList=G.O.blackList.toUpperCase().split('|'); }
6287 if( G.O.whiteList != '' ) { G.whiteList=G.O.whiteList.toUpperCase().split('|'); }
6288
6289 if( G.O.albumList2 !== undefined && G.O.albumList2 !== null && G.O.albumList2.constructor === Array ) {
6290 var l=G.O.albumList2.length;
6291 for(var i=0; i< l; i++ ) {
6292 G.albumList.push(G.O.albumList2[i]);
6293 }
6294 // G.albumList=G.O.albumList.toUpperCase().split('|');
6295 }
6296 if( G.O.albumList2 !== undefined && typeof G.O.albumList2 == 'string' ) {
6297 G.albumList.push(G.O.albumList2);
6298 }
6299
6300
6301 // thumbnail image crop
6302 G.tn.opt.lN.crop = G.O.thumbnailCrop;
6303 G.tn.opt.l1.crop = G.O.thumbnailL1Crop != null ? G.O.thumbnailL1Crop : G.O.thumbnailCrop;
6304
6305
6306 function ThumbnailOpt( lN, l1, opt) {
6307 G.tn.opt.lN[opt]=G.O[lN];
6308 G.tn.opt.l1[opt]=G.O[lN];
6309 if( toType(G.O[l1]) == 'number' ) {
6310 G.tn.opt.l1[opt]=G.O[l1];
6311 }
6312 }
6313 // thumbnail stacks
6314 ThumbnailOpt('thumbnailStacks', 'thumbnailL1Stacks', 'stacks');
6315 // thumbnail stacks translate X
6316 ThumbnailOpt('thumbnailStacksTranslateX', 'thumbnailL1StacksTranslateX', 'stacksTranslateX');
6317 // thumbnail stacks translate Y
6318 ThumbnailOpt('thumbnailStacksTranslateY', 'thumbnailL1StacksTranslateY', 'stacksTranslateY');
6319 // thumbnail stacks translate Z
6320 ThumbnailOpt('thumbnailStacksTranslateZ', 'thumbnailL1StacksTranslateZ', 'stacksTranslateZ');
6321 // thumbnail stacks rotate X
6322 ThumbnailOpt('thumbnailStacksRotateX', 'thumbnailL1StacksRotateX', 'stacksRotateX');
6323 // thumbnail stacks rotate Y
6324 ThumbnailOpt('thumbnailStacksRotateY', 'thumbnailL1StacksRotateY', 'stacksRotateY');
6325 // thumbnail stacks rotate Z
6326 ThumbnailOpt('thumbnailStacksRotateZ', 'thumbnailL1StacksRotateZ', 'stacksRotateZ');
6327 // thumbnail stacks scale
6328 ThumbnailOpt('thumbnailStacksScale', 'thumbnailL1StacksScale', 'stacksScale');
6329 // thumbnail gutter width
6330 ThumbnailOpt('thumbnailGutterWidth', 'thumbnailL1GutterWidth', 'gutterWidth');
6331 // thumbnail gutter height
6332 ThumbnailOpt('thumbnailGutterHeight', 'thumbnailL1GutterHeight', 'gutterHeight');
6333 // thumbnail grid base height (for cascading layout)
6334 ThumbnailOpt('thumbnailBaseGridHeight', 'thumbnailL1BaseGridHeight', 'baseGridHeight');
6335
6336 // gallery display mode
6337 G.galleryDisplayMode.lN = G.O.galleryDisplayMode.toUpperCase();
6338 G.galleryDisplayMode.l1 = G.O.galleryL1DisplayMode != null ? G.O.galleryL1DisplayMode.toUpperCase() : G.O.galleryDisplayMode.toUpperCase();
6339
6340 // gallery maximum number of lines of thumbnails
6341 G.galleryMaxRows.lN = G.O.galleryMaxRows;
6342 G.galleryMaxRows.l1 = toType(G.O.galleryL1MaxRows) == 'number' ? G.O.galleryL1MaxRows : G.O.galleryMaxRows;
6343
6344 // gallery last row full
6345 G.galleryLastRowFull.lN = G.O.galleryLastRowFull;
6346 G.galleryLastRowFull.l1 = G.O.galleryL1LastRowFull != null ? G.O.galleryL1LastRowFull : G.O.galleryLastRowFull;
6347
6348 // gallery sorting
6349 G.gallerySorting.lN = G.O.gallerySorting.toUpperCase();
6350 G.gallerySorting.l1 = G.O.galleryL1Sorting != null ? G.O.galleryL1Sorting.toUpperCase() : G.gallerySorting.lN;
6351
6352 // gallery display transition
6353 G.galleryDisplayTransition.lN = G.O.galleryDisplayTransition.toUpperCase();
6354 G.galleryDisplayTransition.l1 = G.O.galleryL1DisplayTransition != null ? G.O.galleryL1DisplayTransition.toUpperCase() : G.galleryDisplayTransition.lN;
6355
6356 // gallery display transition duration
6357 G.galleryDisplayTransitionDuration.lN = G.O.galleryDisplayTransitionDuration;
6358 G.galleryDisplayTransitionDuration.l1 = G.O.galleryL1DisplayTransitionDuration != null ? G.O.galleryL1DisplayTransitionDuration : G.galleryDisplayTransitionDuration.lN;
6359
6360 // gallery max items per album (not for inline/api defined items)
6361 G.galleryMaxItems.lN = G.O.galleryMaxItems;
6362 G.galleryMaxItems.l1 = toType(G.O.galleryL1MaxItems) == 'number' ? G.O.galleryL1MaxItems : G.O.galleryMaxItems;
6363
6364 // gallery filter tags
6365 G.galleryFilterTags.lN = G.O.galleryFilterTags;
6366 G.galleryFilterTags.l1 = G.O.galleryL1FilterTags != null ? G.O.galleryL1FilterTags : G.O.galleryFilterTags;
6367
6368 // gallery pagination
6369 G.O.galleryPaginationMode = G.O.galleryPaginationMode.toUpperCase();
6370
6371 if( toType(G.O.slideshowDelay) == 'number' && G.O.slideshowDelay >= 2000 ) {
6372 G.VOM.slideshowDelay = G.O.slideshowDelay;
6373 }
6374 else {
6375 NanoConsoleLog(G, 'Parameter "slideshowDelay" must be an integer >= 2000 ms.');
6376 }
6377
6378 // gallery display transition
6379 if( typeof G.O.thumbnailDisplayTransition == 'boolean' ) {
6380 if( G.O.thumbnailDisplayTransition === true ) {
6381 G.tn.opt.lN.displayTransition = 'FADEIN';
6382 G.tn.opt.l1.displayTransition = 'FADEIN';
6383 }
6384 else {
6385 G.tn.opt.lN.displayTransition = 'NONE';
6386 G.tn.opt.l1.displayTransition = 'NONE';
6387 }
6388 }
6389
6390 if( G.O.fnThumbnailDisplayEffect !== '' ) {
6391 G.tn.opt.lN.displayTransition = 'CUSTOM';
6392 G.tn.opt.l1.displayTransition = 'CUSTOM';
6393 }
6394 if( G.O.fnThumbnailL1DisplayEffect !== '' ) {
6395 G.tn.opt.l1.displayTransition = 'CUSTOM';
6396 }
6397
6398 // parse thumbnail display transition
6399 function thumbnailDisplayTransitionParse( cfg, level ) {
6400 if( typeof cfg == 'string' ) {
6401 var st=cfg.split('_');
6402 if( st.length == 1 ) {
6403 G.tn.opt[level]['displayTransition'] = cfg.toUpperCase();
6404 }
6405 if( st.length == 2 ) {
6406 G.tn.opt[level]['displayTransition'] = st[0].toUpperCase();
6407 G.tn.opt[level]['displayTransitionStartVal'] = Number(st[1]);
6408 }
6409 if( st.length == 3 ) {
6410 G.tn.opt[level]['displayTransition'] = st[0].toUpperCase();
6411 G.tn.opt[level]['displayTransitionStartVal'] = Number(st[1]);
6412 G.tn.opt[level]['displayTransitionEasing'] = st[2];
6413 }
6414 }
6415 }
6416 thumbnailDisplayTransitionParse( G.O.thumbnailDisplayTransition, 'lN');
6417 thumbnailDisplayTransitionParse( G.O.thumbnailDisplayTransition, 'l1');
6418 thumbnailDisplayTransitionParse( G.O.thumbnailL1DisplayTransition, 'l1');
6419
6420
6421 // thumbnail display transition duration
6422 ThumbnailOpt('thumbnailDisplayTransitionDuration', 'thumbnailL1DisplayTransitionDuration', 'displayTransitionDuration');
6423 // thumbnail display transition interval duration
6424 ThumbnailOpt('thumbnailDisplayInterval', 'thumbnailL1DisplayInterval', 'displayInterval');
6425
6426
6427 // resolution breakpoints --> convert old syntax to new one
6428 if( G.O.thumbnailSizeSM !== undefined ) { G.O.breakpointSizeSM = G.O.thumbnailSizeSM; }
6429 if( G.O.thumbnailSizeME !== undefined ) { G.O.breakpointSizeME = G.O.thumbnailSizeME; }
6430 if( G.O.thumbnailSizeLA !== undefined ) { G.O.breakpointSizeLA = G.O.thumbnailSizeLA; }
6431 if( G.O.thumbnailSizeXL !== undefined ) { G.O.breakpointSizeXL = G.O.thumbnailSizeXL; }
6432
6433 // THUMBNAIL BUILD INIT
6434 //level 1
6435 if( G.O.thumbnailL1BuildInit2 !== undefined ) {
6436 var t1 = G.O.thumbnailL1BuildInit2.split('|');
6437 for( var i = 0; i < t1.length; i++ ) {
6438 var o1 = t1[i].trim().split('_');
6439 if( o1.length == 3 ) {
6440 var i1 = NewTBuildInit();
6441 i1.element = ThumbnailOverEffectsGetCSSElement(o1[0], '');
6442 i1.property = o1[1];
6443 i1.value = o1[2];
6444 G.tn.buildInit.level1.push(i1);
6445 }
6446 }
6447 }
6448 //level N
6449 if( G.O.thumbnailBuildInit2 !== undefined ) {
6450 var t1 = G.O.thumbnailBuildInit2.split('|');
6451 for( var i = 0; i < t1.length; i++ ) {
6452 var o1 = t1[i].trim().split('_');
6453 if( o1.length == 3 ) {
6454 var i1 = NewTBuildInit();
6455 i1.element = ThumbnailOverEffectsGetCSSElement(o1[0], '');
6456 i1.property = o1[1];
6457 i1.value = o1[2];
6458 G.tn.buildInit.std.push(i1);
6459 }
6460 }
6461 }
6462
6463
6464 // THUMBNAIL HOVER EFFETCS
6465
6466 // thumbnails hover effects - Level1
6467 var tL1HE = G.O.thumbnailL1HoverEffect2;
6468 if( tL1HE !== undefined ) {
6469 switch( toType(tL1HE) ) {
6470 case 'string':
6471 var tmp = tL1HE.split('|');
6472 for(var i = 0; i < tmp.length; i++) {
6473 var oDef = NewTHoverEffect();
6474 oDef = ThumbnailHoverEffectExtract( tmp[i].trim(), oDef );
6475 if( oDef != null ) {
6476 G.tn.hoverEffects.level1.push(oDef);
6477 }
6478 }
6479 break;
6480 case 'object':
6481 var oDef = NewTHoverEffect();
6482 oDef = jQuery.extend(oDef,tL1HE);
6483 oDef = ThumbnailHoverEffectExtract( oDef.name, oDef );
6484 if( oDef != null ) {
6485 G.tn.hoverEffects.level1.push(oDef);
6486 }
6487 break;
6488 case 'array':
6489 for(var i = 0; i < tL1HE.length; i++) {
6490 var oDef = NewTHoverEffect();
6491 oDef = jQuery.extend(oDef,tL1HE[i]);
6492 oDef = ThumbnailHoverEffectExtract( oDef.name, oDef );
6493 if( oDef != null ) {
6494 G.tn.hoverEffects.level1.push(oDef);
6495 }
6496 }
6497 break;
6498 case 'null':
6499 break;
6500 default:
6501 NanoAlert(G, 'incorrect parameter for "thumbnailL1HoverEffect2".');
6502 }
6503 }
6504 G.tn.hoverEffects.level1 = ThumbnailOverEffectsPreset(G.tn.hoverEffects.level1);
6505
6506 // thumbnails hover effects - other levels
6507 var tHE = G.O.thumbnailHoverEffect2;
6508 switch( toType(tHE) ) {
6509 case 'string':
6510 var tmp = tHE.split('|');
6511 for(var i = 0; i < tmp.length; i++) {
6512 var oDef = NewTHoverEffect();
6513 oDef = ThumbnailHoverEffectExtract( tmp[i].trim(), oDef );
6514 if( oDef != null ) {
6515 G.tn.hoverEffects.std.push(oDef);
6516 }
6517 }
6518 break;
6519 case 'object':
6520 var oDef = NewTHoverEffect();
6521 oDef = jQuery.extend(oDef, tHE);
6522 oDef = ThumbnailHoverEffectExtract( oDef.name, oDef );
6523 if( oDef != null ) {
6524 G.tn.hoverEffects.std.push(oDef);
6525 }
6526 break;
6527 case 'array':
6528 for(var i = 0; i < tHE.length; i++) {
6529 var oDef = NewTHoverEffect();
6530 oDef = jQuery.extend(oDef,tHE[i]);
6531 oDef = ThumbnailHoverEffectExtract( oDef.name, oDef );
6532 if( oDef!= null ) {
6533 G.tn.hoverEffects.std.push(oDef);
6534 }
6535 }
6536 break;
6537 case 'null':
6538 break;
6539 default:
6540 NanoAlert(G, 'incorrect parameter for "thumbnailHoverEffect2".');
6541 }
6542 G.tn.hoverEffects.std = ThumbnailOverEffectsPreset(G.tn.hoverEffects.std);
6543
6544
6545 if( G.O.touchAnimationL1 == undefined ) {
6546 G.O.touchAnimationL1 = G.O.touchAnimation;
6547 }
6548
6549 // disable thumbnail touch animation when no hover effect defined
6550 if( G.tn.hoverEffects.std.length == 0 ) {
6551 if( G.tn.hoverEffects.level1.length == 0 ) {
6552 G.O.touchAnimationL1 = false;
6553 }
6554 G.O.touchAnimation = false;
6555 }
6556
6557
6558 // thumbnail sizes
6559 if( G.O.thumbnailHeight == 0 || G.O.thumbnailHeight == '' ) { G.O.thumbnailHeight = 'auto'; }
6560 if( G.O.thumbnailWidth == 0 || G.O.thumbnailWidth == '' ) { G.O.thumbnailWidth = 'auto'; }
6561 if( G.O.thumbnailL1Height == 0 || G.O.thumbnailL1Height == '' ) { G.O.thumbnailL1Height = 'auto'; }
6562 if( G.O.thumbnailL1Width == 0 || G.O.thumbnailL1Width == '' ) { G.O.thumbnailL1Width = 'auto'; }
6563
6564 // RETRIEVE ALL THUMBNAIL SIZES
6565 function ThumbnailSizes( srcOpt, onlyl1, opt) {
6566 if( G.O[srcOpt] == null ) { return; }
6567
6568 if( toType(G.O[srcOpt]) == 'number' ) {
6569 ThumbnailsSetSize( opt, 'l1', G.O[srcOpt], 'u');
6570 if( !onlyl1 ) {
6571 ThumbnailsSetSize( opt, 'lN', G.O[srcOpt], 'u');
6572 }
6573 }
6574 else {
6575 var ws=G.O[srcOpt].split(' ');
6576 var v = 'auto';
6577 if( ws[0].substring(0,4) != 'auto' ) { v=parseInt(ws[0]); }
6578 var c = 'u';
6579 if( ws[0].charAt(ws[0].length - 1) == 'C' ) { c='c'; }
6580 ThumbnailsSetSize( opt, 'l1', v, c ); // default value for all resolutions and navigation levels
6581 if( !onlyl1 ) {
6582 ThumbnailsSetSize( opt, 'lN', v, c );
6583 }
6584 for( var i = 1; i < ws.length; i++ ) {
6585 var r = ws[i].substring(0,2).toLowerCase();
6586 if( /xs|sm|me|la|xl/i.test(r) ) {
6587 var w = ws[i].substring(2);
6588 var v = 'auto';
6589 if( w.substring(0,4) != 'auto' ) { v = parseInt(w); }
6590 var c = 'u';
6591 if( w.charAt(w.length - 1) == 'C' ) { c = 'c'; }
6592 G.tn.settings[opt]['l1'][r] = v;
6593 G.tn.settings[opt]['l1'][r + 'c'] = c;
6594 if( !onlyl1 ) {
6595 G.tn.settings[opt]['lN'][r] = v;
6596 G.tn.settings[opt]['lN'][r + 'c'] = c;
6597 }
6598 }
6599 }
6600 }
6601 }
6602 ThumbnailSizes( 'thumbnailWidth', false, 'width');
6603 ThumbnailSizes( 'thumbnailL1Width', true, 'width');
6604
6605 ThumbnailSizes( 'thumbnailHeight', false, 'height');
6606 ThumbnailSizes( 'thumbnailL1Height', true, 'height');
6607
6608
6609 G.O.thumbnailBorderHorizontal = parseInt(G.O.thumbnailBorderHorizontal);
6610 G.O.thumbnailBorderVertical = parseInt(G.O.thumbnailBorderVertical);
6611 G.O.thumbnailLabelHeight = parseInt(G.O.thumbnailLabelHeight);
6612
6613
6614 // retrieve all mosaic layout patterns
6615 // default pattern
6616 if( G.O.galleryMosaic != undefined ) {
6617 // clone object
6618 G.tn.settings.mosaic.l1.xs = JSON.parse(JSON.stringify(G.O.galleryMosaic));
6619 G.tn.settings.mosaic.l1.sm = JSON.parse(JSON.stringify(G.O.galleryMosaic));
6620 G.tn.settings.mosaic.l1.me = JSON.parse(JSON.stringify(G.O.galleryMosaic));
6621 G.tn.settings.mosaic.l1.la = JSON.parse(JSON.stringify(G.O.galleryMosaic));
6622 G.tn.settings.mosaic.l1.xl = JSON.parse(JSON.stringify(G.O.galleryMosaic));
6623 G.tn.settings.mosaic.lN.xs = JSON.parse(JSON.stringify(G.O.galleryMosaic));
6624 G.tn.settings.mosaic.lN.sm = JSON.parse(JSON.stringify(G.O.galleryMosaic));
6625 G.tn.settings.mosaic.lN.me = JSON.parse(JSON.stringify(G.O.galleryMosaic));
6626 G.tn.settings.mosaic.lN.la = JSON.parse(JSON.stringify(G.O.galleryMosaic));
6627 G.tn.settings.mosaic.lN.xl = JSON.parse(JSON.stringify(G.O.galleryMosaic));
6628 G.tn.settings.mosaicCalcFactor('l1', 'xs');
6629 G.tn.settings.mosaicCalcFactor('l1', 'sm');
6630 G.tn.settings.mosaicCalcFactor('l1', 'me');
6631 G.tn.settings.mosaicCalcFactor('l1', 'la');
6632 G.tn.settings.mosaicCalcFactor('l1', 'xl');
6633 G.tn.settings.mosaicCalcFactor('lN', 'xs');
6634 G.tn.settings.mosaicCalcFactor('lN', 'sm');
6635 G.tn.settings.mosaicCalcFactor('lN', 'me');
6636 G.tn.settings.mosaicCalcFactor('lN', 'la');
6637 G.tn.settings.mosaicCalcFactor('lN', 'xl');
6638 }
6639 if( G.O.galleryL1Mosaic != undefined ) {
6640 // default L1 pattern
6641 G.tn.settings.mosaic.l1.xs = JSON.parse(JSON.stringify(G.O.galleryL1Mosaic));
6642 G.tn.settings.mosaic.l1.sm = JSON.parse(JSON.stringify(G.O.galleryL1Mosaic));
6643 G.tn.settings.mosaic.l1.me = JSON.parse(JSON.stringify(G.O.galleryL1Mosaic));
6644 G.tn.settings.mosaic.l1.la = JSON.parse(JSON.stringify(G.O.galleryL1Mosaic));
6645 G.tn.settings.mosaic.l1.xl = JSON.parse(JSON.stringify(G.O.galleryL1Mosaic));
6646 G.tn.settings.mosaicCalcFactor('l1', 'xs');
6647 G.tn.settings.mosaicCalcFactor('l1', 'sm');
6648 G.tn.settings.mosaicCalcFactor('l1', 'me');
6649 G.tn.settings.mosaicCalcFactor('l1', 'la');
6650 G.tn.settings.mosaicCalcFactor('l1', 'xl');
6651 }
6652 for( var w = 0; w < G.tn.settings.mosaic.l1; w++ ) {
6653 if( G.O['galleryMosaic' + G.tn.settings.mosaic.l1[w].toUpperCase()] != undefined ) {
6654 G.tn.settings.mosaic.lN[tn.settings.mosaic.l1[w]] = JSON.parse(JSON.stringify( G.O['galleryMosaic' + G.tn.settings.mosaic.l1[w].toUpperCase()] ));
6655 G.tn.settings.mosaic.l1[tn.settings.mosaic.l1[w]] = JSON.parse(JSON.stringify( G.O['galleryMosaic' + G.tn.settings.mosaic.l1[w].toUpperCase()] ));
6656 G.tn.settings.mosaicCalcFactor('l1', G.tn.settings.mosaic.l1[w]);
6657 G.tn.settings.mosaicCalcFactor('lN', G.tn.settings.mosaic.l1[w]);
6658 }
6659 }
6660 for( var w = 0; w < G.tn.settings.mosaic.l1; w++ ) {
6661 if( G.O['galleryL1Mosaic'+G.tn.settings.mosaic.l1[w].toUpperCase()] != undefined ) {
6662 G.tn.settings.mosaic.l1[tn.settings.mosaic.l1[w]] = JSON.parse(JSON.stringify( G.O['galleryL1Mosaic'+G.tn.settings.mosaic.l1[w].toUpperCase()] ));
6663 G.tn.settings.mosaicCalcFactor('l1', G.tn.settings.mosaic.l1[w]);
6664 }
6665 }
6666
6667 G.O.imageTransition = G.O.imageTransition.toUpperCase();
6668
6669 G.layout.SetEngine();
6670
6671 // init plugins
6672 switch( G.O.kind ) {
6673 // MARKUP / API
6674 case '':
6675 break;
6676 // JSON, Flickr, Picasa, ...
6677 default:
6678 jQuery.nanogallery2['data_' + G.O.kind](G, 'Init' );
6679 }
6680
6681 }
6682
6683 // HOVER EFFECTS
6684 function ThumbnailHoverEffectExtract( name, effect) {
6685 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'];
6686
6687 var sp = name.split('_');
6688 if( sp.length >= 4 ) {
6689 // var oDef=NewTHoverEffect();
6690 effect.name = '';
6691 effect.type = sp[1];
6692 effect.from = sp[2];
6693 effect.to = sp[3];
6694 if( sp.length >= 5 ) {
6695 // effect.duration=sp[4];
6696
6697 for( var n = 4; n < sp.length; n++ ) {
6698 var v = sp[n];
6699
6700 // check if an easing name
6701 var foundEasing = false;
6702 for( var e = 0; e < easings.length; e++) {
6703 if( v == easings[e] ) {
6704 foundEasing = true;
6705 effect.easing = v;
6706 break;
6707 }
6708 }
6709 if( foundEasing === true ) {
6710 continue;
6711 }
6712
6713 v = v.toUpperCase();
6714
6715 if( v == 'HOVERIN' ) {
6716 effect.hoverout = false;
6717 continue;
6718 }
6719 if( v == 'HOVEROUT' ) {
6720 effect.hoverin = false;
6721 continue;
6722 }
6723
6724 if( v == 'KEYFRAME' ) {
6725 effect.firstKeyframe = false;
6726 continue;
6727 }
6728
6729 var num = parseInt(v.replace(/[^0-9\.]/g, ''), 10); // extract a number if one exists
6730
6731 if( num > 0 ) {
6732 // the string contains a numbers > 0
6733 if( v.indexOf('DURATION') >= 0 ) {
6734 effect.duration = num;
6735 continue;
6736 }
6737 if( v.indexOf('DURATIONBACK') >= 0 ) {
6738 effect.durationBack = num;
6739 continue;
6740 }
6741 if( v.indexOf('DELAY') >= 0 ) {
6742 effect.delay = num;
6743 continue;
6744 }
6745 if( v.indexOf('DELAYBACK') >= 0 ) {
6746 effect.delayBack = num;
6747 continue;
6748 }
6749
6750 // no parameter name found -> default is duration
6751 effect.duration = num;
6752 }
6753 }
6754 }
6755 effect.element=ThumbnailOverEffectsGetCSSElement(sp[0], effect.type);
6756
6757 }
6758 else {
6759 effect.name = name;
6760 // NanoAlert(G, 'incorrect parameter for "thumbnailHoverEffect": ' + name);
6761 // return null;
6762 }
6763 return effect;
6764 }
6765
6766
6767 function ThumbnailOverEffectsGetCSSElement( element, property ) {
6768 var r = element;
6769
6770 switch ( element ) {
6771 case 'image':
6772 if( property == 'blur' || property == 'brightness' || property == 'grayscale' || property == 'sepia' || property == 'contrast' || property == 'opacity'|| property == 'saturate' ) {
6773 // r='.nGY2GThumbnailImg';
6774 r = '.nGY2GThumbnailImage';
6775 }
6776 else {
6777 r = '.nGY2GThumbnailImage';
6778 }
6779 break;
6780 case 'thumbnail':
6781 r = '.nGY2GThumbnail';
6782 break;
6783 case 'label':
6784 r = '.nGY2GThumbnailLabel';
6785 break;
6786 case 'title':
6787 r = '.nGY2GThumbnailTitle';
6788 break;
6789 case 'description':
6790 r = '.nGY2GThumbnailDescription';
6791 break;
6792 case 'tools':
6793 r = '.nGY2GThumbnailIcons';
6794 break;
6795 case 'customlayer':
6796 r = '.nGY2GThumbnailCustomLayer';
6797 break;
6798 }
6799 return r;
6800 }
6801
6802 // convert preset hover effects to new ones (nanogallery2)
6803 function ThumbnailOverEffectsPreset( effects ) {
6804
6805 // COMPATIBILITY WITH nanoGALLERY
6806 // OK:
6807 // 'borderLighter', 'borderDarker', 'scale120', 'labelAppear', 'labelAppear75', 'labelOpacity50', 'scaleLabelOverImage'
6808 // 'overScale', 'overScaleOutside', 'descriptionAppear'
6809 // 'slideUp', 'slideDown', 'slideRight', 'slideLeft'
6810 // 'imageScale150', 'imageScaleIn80', 'imageScale150Outside', 'imageSlideUp', 'imageSlideDown', 'imageSlideRight', 'imageSlideLeft'
6811 // 'labelSlideUpTop', 'labelSlideUp', 'labelSlideDown', 'descriptionSlideUp'
6812 // KO:
6813 // 'labelSplit4', 'labelSplitVert', 'labelAppearSplit4', 'labelAppearSplitVert'
6814 // TODO:
6815 // 'rotateCornerBL', 'rotateCornerBR', 'imageSplit4', 'imageSplitVert', 'imageRotateCornerBL', 'imageRotateCornerBR', 'imageFlipHorizontal', 'imageFlipVertical'
6816
6817
6818
6819 var newEffects=[];
6820 for( var i=0; i< effects.length; i++ ) {
6821 switch( effects[i].name.toUpperCase() ) {
6822 case 'BORDERLIGHTER':
6823 // var color=ngtinycolor(GalleryThemeGetCurrent().thumbnail.borderColor);
6824 // name='thumbnail_borderColor_'+color.toRgbString()+'_'+color.lighten(50).toRgbString();
6825
6826 var rgb = ColorHelperToRGB(GalleryThemeGetCurrent().thumbnail.borderColor);
6827 name = 'thumbnail_borderColor_'+rgb+'_'+ShadeBlendConvert(0.5, rgb );
6828 newEffects.push(ThumbnailHoverEffectExtract(name, effects[i]));
6829 break;
6830 case 'BORDERDARKER':
6831 // var color=ngtinycolor(GalleryThemeGetCurrent().thumbnail.borderColor);
6832 // name='thumbnail_borderColor_'+color.toRgbString()+'_'+color.darken(50).toRgbString();
6833 var rgb = ColorHelperToRGB(GalleryThemeGetCurrent().thumbnail.borderColor);
6834 name = 'thumbnail_borderColor_'+rgb+'_'+ShadeBlendConvert(-0.5, rgb );
6835 newEffects.push(ThumbnailHoverEffectExtract(name, effects[i]));
6836 break;
6837 case 'SCALE120':
6838 newEffects.push(ThumbnailHoverEffectExtract('thumbnail_scale_1.00_1.20', effects[i]));
6839 break;
6840 case 'LABELAPPEAR':
6841 case 'LABELAPPEAR75':
6842 newEffects.push(ThumbnailHoverEffectExtract('label_opacity_0.00_1.00', effects[i]));
6843 break;
6844 case 'TOOLSAPPEAR':
6845 newEffects.push(ThumbnailHoverEffectExtract('tools_opacity_0_1', effects[i]));
6846 break;
6847 case 'TOOLSSLIDEDOWN':
6848 newEffects.push(ThumbnailHoverEffectExtract('tools_translateY_-100%_0%', effects[i]));
6849 break;
6850 case 'TOOLSSLIDEUP':
6851 newEffects.push(ThumbnailHoverEffectExtract('tools_translateY_100%_0%', effects[i]));
6852 break;
6853 case 'LABELOPACITY50':
6854 newEffects.push(ThumbnailHoverEffectExtract('label_opacity_1.00_0.50', effects[i]));
6855 break;
6856 case 'LABELSLIDEUPTOP':
6857 case 'LABELSLIDEUP':
6858 newEffects.push(ThumbnailHoverEffectExtract('label_translateY_100%_0%', effects[i]));
6859 break;
6860 case 'LABELSLIDEDOWN':
6861 newEffects.push(ThumbnailHoverEffectExtract('label_translateY_-100%_0%', effects[i]));
6862 break;
6863 case 'SCALELABELOVERIMAGE':
6864 newEffects.push(ThumbnailHoverEffectExtract('label_scale_0.00_1.00', effects[i]));
6865 var n = cloneJSObject(effects[i]);
6866 newEffects.push(ThumbnailHoverEffectExtract('image_scale_1.00_0.00', n));
6867 break;
6868 case 'OVERSCALE':
6869 case 'OVERSCALEOUTSIDE':
6870 name = 'label_scale_0_100';
6871 newEffects.push(ThumbnailHoverEffectExtract('label_scale_2.00_1.00', effects[i]));
6872 var n = cloneJSObject(effects[i]);
6873 newEffects.push(ThumbnailHoverEffectExtract('label_opacity_0.00_1.00', n));
6874 n = cloneJSObject(effects[i]);
6875 newEffects.push(ThumbnailHoverEffectExtract('image_scale_1.00_0.00', n));
6876 n = cloneJSObject(effects[i]);
6877 newEffects.push(ThumbnailHoverEffectExtract('image_opacity_1.00_0.00', n));
6878 break;
6879 case 'DESCRIPTIONAPPEAR':
6880 newEffects.push(ThumbnailHoverEffectExtract('description_opacity_0_1', effects[i]));
6881 break;
6882 case 'SLIDERIGHT':
6883 newEffects.push(ThumbnailHoverEffectExtract('image_translateX_0%_100%', effects[i]));
6884 newEffects.push(ThumbnailHoverEffectExtract('label_translateX_-100%_0%', cloneJSObject(effects[i])));
6885 break;
6886 case 'SLIDELEFT':
6887 newEffects.push(ThumbnailHoverEffectExtract('image_translateX_0%_-100%', effects[i]));
6888 newEffects.push(ThumbnailHoverEffectExtract('label_translateX_100%_0%', cloneJSObject(effects[i])));
6889 break;
6890 case 'SLIDEUP':
6891 newEffects.push(ThumbnailHoverEffectExtract('image_translateY_0%_-100%', effects[i]));
6892 newEffects.push(ThumbnailHoverEffectExtract('label_translateY_100%_0%', cloneJSObject(effects[i])));
6893 break;
6894 case 'SLIDEDOWN':
6895 newEffects.push(ThumbnailHoverEffectExtract('image_translateY_0%_100%', effects[i]));
6896 newEffects.push(ThumbnailHoverEffectExtract('label_translateY_-100%_0%', cloneJSObject(effects[i])));
6897 break;
6898 case 'IMAGESCALE150':
6899 case 'IMAGESCALE150OUTSIDE':
6900 newEffects.push(ThumbnailHoverEffectExtract('image_scale_1.00_1.50', effects[i]));
6901 break;
6902 case 'IMAGESCALEIN80':
6903 newEffects.push(ThumbnailHoverEffectExtract('image_scale_1.20_1.00', effects[i]));
6904 break;
6905 case 'IMAGESLIDERIGHT':
6906 newEffects.push(ThumbnailHoverEffectExtract('image_translateX_0%_100%', effects[i]));
6907 break;
6908 case 'IMAGESLIDELEFT':
6909 newEffects.push(ThumbnailHoverEffectExtract('image_translateX_0%_-100%', effects[i]));
6910 break;
6911 case 'IMAGESLIDEUP':
6912 newEffects.push(ThumbnailHoverEffectExtract('image_translateY_0%_-100%', effects[i]));
6913 break;
6914 case 'IMAGESLIDEDOWN':
6915 newEffects.push(ThumbnailHoverEffectExtract('image_translateY_0%_100%', effects[i]));
6916 break;
6917 case 'LABELSLIDEUP':
6918 case 'LABELSLIDEUPTOP':
6919 newEffects.push(ThumbnailHoverEffectExtract('label_translateY_100%_0%', effects[i]));
6920 break;
6921 case 'LABELSLIDEUPDOWN':
6922 newEffects.push(ThumbnailHoverEffectExtract('label_translateY_0%_100%', effects[i]));
6923 break;
6924 case 'DESCRIPTIONSLIDEUP':
6925 newEffects.push(ThumbnailHoverEffectExtract('description_translateY_110%_0%', effects[i]));
6926 break;
6927
6928 case 'IMAGEBLURON':
6929 newEffects.push(ThumbnailHoverEffectExtract('image_blur_2.00px_0.00px', effects[i]));
6930 break;
6931 case 'IMAGEBLUROFF':
6932 newEffects.push(ThumbnailHoverEffectExtract('image_blur_0.00px_2.00px', effects[i]));
6933 break;
6934 case 'IMAGEGRAYON':
6935 newEffects.push(ThumbnailHoverEffectExtract('image_grayscale_0%_100%', effects[i]));
6936 break;
6937 case 'IMAGEGRAYOFF':
6938 newEffects.push(ThumbnailHoverEffectExtract('image_grayscale_100%_0%', effects[i]));
6939 break;
6940 case 'IMAGESEPIAON':
6941 newEffects.push(ThumbnailHoverEffectExtract('image_sepia_100%_1%', effects[i]));
6942 break;
6943 case 'IMAGESEPIAOFF':
6944 newEffects.push(ThumbnailHoverEffectExtract('image_sepia_1%_100%', effects[i]));
6945 break;
6946
6947 default:
6948 newEffects.push(effects[i]);
6949 break;
6950 }
6951 }
6952
6953 return newEffects;
6954 }
6955
6956
6957 // Thumbnail hover effect definition
6958 function NewTHoverEffect() {
6959 var oDef={
6960 name: '',
6961 element: '', // element class
6962 type: '',
6963 from: '', // start value
6964 to: '', // end value
6965 hoverin: true,
6966 hoverout: true,
6967 firstKeyframe: true,
6968 delay: 0,
6969 delayBack: 0,
6970 duration: 400,
6971 durationBack: 300,
6972 easing: 'easeOutQuart',
6973 easingBack: 'easeOutQuart',
6974 animParam: null
6975 };
6976 return oDef;
6977 }
6978
6979 function NewTBuildInit() {
6980 // to set CSS properties
6981 var oDef={ element: '', property: '', value: '' };
6982 return oDef;
6983 }
6984
6985
6986 function ThumbnailStyle( cfg, level) {
6987
6988 switch( cfg.position ){
6989 case 'onBottom' :
6990 G.tn.style[level]['label'] = 'bottom:0; ';
6991 break;
6992 case 'overImageOnTop' :
6993 G.tn.style[level]['label'] = 'top:0; position:absolute;';
6994 break;
6995 case 'overImageOnMiddle' :
6996 G.tn.style[level]['label'] = 'top:0; bottom:0;';
6997 G.tn.style[level]['title'] = 'position:absolute; bottom:50%;';
6998 G.tn.style[level]['desc'] = 'position:absolute; top:50%;';
6999 break;
7000 case 'custom' :
7001 break;
7002 case 'overImageOnBottom' :
7003 default :
7004 G.O.thumbnailLabel.position = 'overImageOnBottom';
7005 G.tn.style[level].label = 'bottom:0; position:absolute;';
7006 break;
7007 }
7008 // if( G.layout.engine != 'CASCADING' ) {
7009 if( cfg.position != 'onBottom' ) {
7010 // multi-line
7011 if( cfg.titleMultiLine ) {
7012 G.tn.style[level]['title'] = 'white-space:normal;';
7013 }
7014 if( cfg.descriptionMultiLine ) {
7015 G.tn.style[level]['desc'] = 'white-space:normal;';
7016 }
7017 }
7018
7019
7020 switch( cfg.align ) {
7021 case 'right':
7022 G.tn.style[level].label += 'text-align:right;';
7023 break;
7024 case 'left':
7025 G.tn.style[level].label += 'text-align:left;';
7026 break;
7027 default:
7028 G.tn.style[level].label += 'text-align:center;';
7029 break;
7030 }
7031 if( cfg.titleFontSize != undefined && cfg.titleFontSize != '' ) {
7032 G.tn.style[level].title += 'font-size:' + cfg.titleFontSize + ';';
7033 }
7034 if( cfg.descriptionFontSize != undefined && cfg.descriptionFontSize != '' ) {
7035 G.tn.style[level].desc += 'font-size:' + cfg.descriptionFontSize + ';';
7036 }
7037
7038 if( cfg.displayDescription == false ) {
7039 G.tn.style[level].desc += 'display:none;';
7040 }
7041 }
7042
7043
7044 // cache some thumbnail settings
7045 function ThumbnailDefCaches() {
7046 // thumbnail content CSS styles
7047
7048 // settings for level L1 and LN
7049 ThumbnailStyle( G.O.thumbnailLabel, 'lN');
7050 ThumbnailStyle( G.O.thumbnailLabel, 'l1');
7051
7052 if( G.O.thumbnailL1Label && G.O.thumbnailL1Label.display ) {
7053 // settings for level L1
7054 ThumbnailStyle( G.O.thumbnailL1Label, 'l1');
7055 }
7056
7057 G.tn.borderWidth = G.O.thumbnailBorderHorizontal;
7058 G.tn.borderHeight = G.O.thumbnailBorderVertical;
7059
7060
7061 // default thumbnail sizes levels l1 and lN
7062 var lst=['xs','sm','me','la','xl'];
7063 for( var i = 0; i < lst.length; i++ ) {
7064 var w = G.tn.settings.width.lN[lst[i]];
7065 if( w != 'auto' ) {
7066 G.tn.defaultSize.width.lN[lst[i]] = w;
7067 G.tn.defaultSize.width.l1[lst[i]] = w;
7068 }
7069 else {
7070 var h = G.tn.settings.height.lN[lst[i]];
7071 G.tn.defaultSize.width.lN[lst[i]] = h; // dynamic width --> set height value as default for the width
7072 G.tn.defaultSize.width.l1[lst[i]] = h; // dynamic width --> set height value as default
7073 }
7074 }
7075 for( var i = 0; i < lst.length; i++ ) {
7076 var h = G.tn.settings.height.lN[lst[i]];
7077 if( h != 'auto' ) {
7078 // grid or justified layout
7079 G.tn.defaultSize.height.lN[lst[i]] = h; //+G.tn.labelHeight.get();
7080 G.tn.defaultSize.height.l1[lst[i]] = h; //+G.tn.labelHeight.get();
7081 }
7082 else {
7083 var w = G.tn.settings.width.lN[lst[i]];
7084 G.tn.defaultSize.height.lN[lst[i]] = w; // dynamic height --> set width value as default for the height
7085 G.tn.defaultSize.height.l1[lst[i]] = w; // dynamic height --> set width value as default
7086 }
7087 }
7088
7089 // default thumbnail sizes levels l1
7090 for( var i = 0; i < lst.length; i++ ) {
7091 var w = G.tn.settings.width.l1[lst[i]];
7092 if( w != 'auto' ) {
7093 G.tn.defaultSize.width.l1[lst[i]] = w;
7094 }
7095 else {
7096 var h = G.tn.settings.height.l1[lst[i]];
7097 G.tn.defaultSize.width.l1[lst[i]] = h; // dynamic width --> set height value as default
7098 }
7099 }
7100 for( var i = 0; i < lst.length; i++ ) {
7101 var h = G.tn.settings.height.l1[lst[i]];
7102 if( h != 'auto' ) {
7103 // grid or justified layout
7104 G.tn.defaultSize.height.l1[lst[i]] = h; //+G.tn.labelHeight.get();
7105 }
7106 else {
7107 var w = G.tn.settings.width.l1[lst[i]];
7108 G.tn.defaultSize.height.l1[lst[i]]= w ; // dynamic height --> set width value as default
7109 }
7110 }
7111
7112 }
7113
7114
7115 // ##### THUMBNAIL SIZE MANAGEMENT
7116 function ThumbnailsSetSize( dir, level, v, crop ) {
7117 G.tn.settings[dir][level]['xs'] = v;
7118 G.tn.settings[dir][level]['sm'] = v;
7119 G.tn.settings[dir][level]['me'] = v;
7120 G.tn.settings[dir][level]['la'] = v;
7121 G.tn.settings[dir][level]['xl'] = v;
7122 G.tn.settings[dir][level]['xsc'] = crop;
7123 G.tn.settings[dir][level]['smc'] = crop;
7124 G.tn.settings[dir][level]['mec'] = crop;
7125 G.tn.settings[dir][level]['lac'] = crop;
7126 G.tn.settings[dir][level]['xlc'] = crop;
7127 }
7128
7129
7130 //
7131 function GalleryThemeGetCurrent() {
7132
7133 var cs=null;
7134 switch(toType(G.O.galleryTheme)) {
7135 case 'object': // user custom color scheme object
7136 cs = G.galleryTheme_dark; // default color scheme
7137 jQuery.extend(true,cs,G.O.galleryTheme);
7138 break;
7139 case 'string': // name of an internal defined color scheme
7140 switch( G.O.galleryTheme ) {
7141 case 'light':
7142 cs = G.galleryTheme_light;
7143 break;
7144 case 'default':
7145 case 'dark':
7146 case 'none':
7147 default:
7148 cs = G.galleryTheme_dark;
7149 }
7150 break;
7151 default:
7152 cs = G.galleryTheme_dark;
7153 }
7154 return cs;
7155 }
7156
7157 // ##### BREADCRUMB/THUMBNAIL COLOR SCHEME #####
7158 function SetGalleryTheme() {
7159
7160 if( typeof G.O.colorScheme !== 'undefined' ) {
7161 G.O.galleryTheme = G.O.colorScheme;
7162 }
7163
7164 var cs = null;
7165 var galleryTheme = '';
7166 switch(toType(G.O.galleryTheme)) {
7167 case 'object': // user custom color scheme object
7168 cs = G.galleryTheme_dark; // default color scheme
7169 jQuery.extend(true,cs,G.O.galleryTheme);
7170 galleryTheme='nanogallery_gallerytheme_custom_' + G.baseEltID;
7171 break;
7172 case 'string': // name of an internal defined color scheme
7173 switch( G.O.galleryTheme ) {
7174 case 'light':
7175 cs = G.galleryTheme_light;
7176 galleryTheme='nanogallery_gallerytheme_light_' + G.baseEltID;
7177 break;
7178 case 'default':
7179 case 'dark':
7180 case 'none':
7181 default:
7182 cs = G.galleryTheme_dark;
7183 galleryTheme='nanogallery_gallerytheme_dark_' + G.baseEltID;
7184 }
7185 break;
7186 default:
7187 NanoAlert(G, 'Error in galleryTheme parameter.');
7188 return;
7189 }
7190
7191 //var s1='.nanogallery_theme_'+G.O.theme+' ';
7192 var s1='.' + galleryTheme + ' ';
7193
7194 // navigation bar
7195 var s=s1+'.nGY2Navigationbar { background:'+cs.navigationBar.background+'; }'+'\n';
7196 if( cs.navigationBar.border !== undefined && cs.navigationBar.border !== '' ) { s+=s1+'.nGY2Navigationbar { border:'+cs.navigationBar.border+'; }'+'\n'; }
7197 if( cs.navigationBar.borderTop !== undefined && cs.navigationBar.borderTop !== '' ) { s+=s1+'.nGY2Navigationbar { border-top:'+cs.navigationBar.borderTop+'; }'+'\n'; }
7198 if( cs.navigationBar.borderBottom !== undefined && cs.navigationBar.borderBottom !== '' ) { s+=s1+'.nGY2Navigationbar { border-bottom:'+cs.navigationBar.borderBottom+'; }'+'\n'; }
7199 if( cs.navigationBar.borderRight !== undefined && cs.navigationBar.borderRight !== '' ) { s+=s1+'.nGY2Navigationbar { border-right:'+cs.navigationBar.borderRight+'; }'+'\n'; }
7200 if( cs.navigationBar.borderLeft !== undefined && cs.navigationBar.borderLeft !== '' ) { s+=s1+'.nGY2Navigationbar { border-left:'+cs.navigationBar.borderLeft+'; }'+'\n'; }
7201
7202 // navigation bar - breadcrumb
7203 s+=s1+'.nGY2Breadcrumb { background:'+cs.navigationBreadcrumb.background+'; border-radius:'+cs.navigationBreadcrumb.borderRadius+'; }'+'\n';
7204 s+=s1+'.nGY2Breadcrumb .oneItem { color:'+cs.navigationBreadcrumb.color+'; }'+'\n';
7205 s+=s1+'.nGY2Breadcrumb .oneItem:hover { color:'+cs.navigationBreadcrumb.colorHover+'; }'+'\n';
7206
7207 // navigation bar - tag filter
7208 s+=s1+'.nGY2NavFilterUnselected { color:'+cs.navigationFilter.color+'; background:'+cs.navigationFilter.background+'; border-radius:'+cs.navigationFilter.borderRadius+'; }'+'\n';
7209 s+=s1+'.nGY2NavFilterSelected { color:'+cs.navigationFilter.colorSelected+'; background:'+cs.navigationFilter.backgroundSelected+'; border-radius:'+cs.navigationFilter.borderRadius+'; }'+'\n';
7210 s+=s1+'.nGY2NavFilterSelectAll { color:'+cs.navigationFilter.colorSelected+'; background:'+cs.navigationFilter.background+'; border-radius:'+cs.navigationFilter.borderRadius+'; }'+'\n';
7211
7212 // thumbnails
7213 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';
7214 s+=s1+'.nGY2GThumbnailStack { background:'+cs.thumbnail.stackBackground+'; }'+'\n';
7215 // s+=s1+'.nGY2GThumbnailImage { background:'+cs.thumbnail.background+'; background-image:'+cs.thumbnail.backgroundImage+'; }'+'\n';
7216 s+=s1+'.nGY2TnImgBack { background:'+cs.thumbnail.background+'; background-image:'+cs.thumbnail.backgroundImage+'; }'+'\n';
7217 s+=s1+'.nGY2GThumbnailAlbumUp { background:'+cs.thumbnail.background+'; background-image:'+cs.thumbnail.backgroundImage+'; color:'+cs.thumbnail.titleColor+'; }'+'\n';
7218 s+=s1+'.nGY2GThumbnailIconsFullThumbnail { color:'+cs.thumbnail.titleColor+'; }\n';
7219 s+=s1+'.nGY2GThumbnailLabel { background:'+cs.thumbnail.labelBackground+'; opacity:'+cs.thumbnail.labelOpacity+'; }'+'\n';
7220 s+=s1+'.nGY2GThumbnailImageTitle { color:'+cs.thumbnail.titleColor+'; background-color:'+cs.thumbnail.titleBgColor+'; '+(cs.thumbnail.titleShadow =='' ? '': 'Text-Shadow:'+cs.thumbnail.titleShadow+';')+' }'+'\n';
7221 s+=s1+'.nGY2GThumbnailAlbumTitle { color:'+cs.thumbnail.titleColor+'; background-color:'+cs.thumbnail.titleBgColor+'; '+(cs.thumbnail.titleShadow =='' ? '': 'Text-Shadow:'+cs.thumbnail.titleShadow+';')+' }'+'\n';
7222 s+=s1+'.nGY2GThumbnailDescription { color:'+cs.thumbnail.descriptionColor+'; background-color:'+cs.thumbnail.descriptionBgColor+'; '+(cs.thumbnail.descriptionShadow =='' ? '': 'Text-Shadow:'+cs.thumbnail.descriptionShadow+';')+' }'+'\n';
7223
7224 // thumbnails - icons
7225 s+=s1+'.nGY2GThumbnailIcons { padding:'+cs.thumbnailIcon.padding+'; }\n';
7226 s+=s1+'.nGY2GThumbnailIcon { color:'+cs.thumbnailIcon.color+'; }\n';
7227 s+=s1+'.nGY2GThumbnailIconTextBadge { background-color:'+cs.thumbnailIcon.color+'; }\n';
7228
7229 // gallery pagination -> dot/rectangle based
7230 if( G.O.galleryPaginationMode != 'NUMBERS' ) {
7231 s+=s1+'.nGY2paginationDot { border:'+cs.pagination.shapeBorder+'; background:'+cs.pagination.shapeColor+';}\n';
7232 s+=s1+'.nGY2paginationDotCurrentPage { border:'+cs.pagination.shapeBorder+'; background:'+cs.pagination.shapeSelectedColor+';}\n';
7233 s+=s1+'.nGY2paginationRectangle { border:'+cs.pagination.shapeBorder+'; background:'+cs.pagination.shapeColor+';}\n';
7234 s+=s1+'.nGY2paginationRectangleCurrentPage { border:'+cs.pagination.shapeBorder+'; background:'+cs.pagination.shapeSelectedColor+';}\n';
7235 } else {
7236 s+=s1+'.nGY2paginationItem { background:'+cs.pagination.background+'; color:'+cs.pagination.color+'; border-radius:'+cs.pagination.borderRadius+'; }\n';
7237 s+=s1+'.nGY2paginationItemCurrentPage { background:'+cs.pagination.background+'; color:'+cs.pagination.color+'; border-radius:'+cs.pagination.borderRadius+'; }\n';
7238 s+=s1+'.nGY2PaginationPrev { background:'+cs.pagination.background+'; color:'+cs.pagination.color+'; border-radius:'+cs.pagination.borderRadius+'; }\n';
7239 s+=s1+'.nGY2PaginationNext { background:'+cs.pagination.background+'; color:'+cs.pagination.color+'; border-radius:'+cs.pagination.borderRadius+'; }\n';
7240 s+=s1+'.nGY2paginationItemCurrentPage { background:'+cs.pagination.backgroundSelected+'; }\n';
7241 }
7242
7243 // gallery more button
7244 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';
7245 s+=s1+'.nGY2GalleryMoreButtonAnnotation { color:'+cs.thumbnail.titleColor+'; '+(cs.thumbnail.titleShadow =='' ? '': 'Text-Shadow:'+cs.thumbnail.titleShadow)+'; }\n';
7246
7247 jQuery('head').append('<style id="ngycs_'+G.baseEltID+'">'+s+'</style>');
7248 G.$E.base.addClass(galleryTheme);
7249
7250 };
7251
7252 // ##### VIEWER COLOR SCHEME #####
7253 function SetViewerTheme( ) {
7254
7255 if( G.VOM.viewerTheme != '' ) {
7256 G.VOM.$cont.addClass(G.VOM.viewerTheme);
7257 return;
7258 }
7259
7260 if( typeof G.O.colorSchemeViewer !== 'undefined' ) {
7261 G.O.viewerTheme = G.O.colorSchemeViewer;
7262 }
7263
7264 var cs=null;
7265 switch(toType(G.O.viewerTheme)) {
7266 case 'object': // user custom color scheme object
7267 cs = G.viewerTheme_dark;
7268 jQuery.extend(true, cs, G.O.viewerTheme);
7269 G.VOM.viewerTheme = 'nanogallery_viewertheme_custom_' + G.baseEltID;
7270 break;
7271 case 'string': // name of an internal defined color scheme
7272 switch( G.O.viewerTheme ) {
7273 case 'none':
7274 return;
7275 break;
7276 case 'light':
7277 cs = G.viewerTheme_light;
7278 G.VOM.viewerTheme = 'nanogallery_viewertheme_light_' + G.baseEltID;
7279 break;
7280 case 'border':
7281 cs = G.viewerTheme_border;
7282 G.VOM.viewerTheme = 'nanogallery_viewertheme_border_' + G.baseEltID;
7283 break;
7284 case 'dark':
7285 case 'default':
7286 cs = G.viewerTheme_dark;
7287 G.VOM.viewerTheme = 'nanogallery_viewertheme_dark_' + G.baseEltID;
7288 break;
7289 }
7290 break;
7291 default:
7292 NanoAlert(G, 'Error in viewerTheme parameter.');
7293 return;
7294 }
7295
7296 var s1 = '.' + G.VOM.viewerTheme + ' ';
7297 var s = s1 + '.nGY2Viewer { background:' + cs.background + '; }'+'\n';
7298 s += s1 + '.nGY2ViewerMedia { border:' + cs.imageBorder + '; box-shadow:' + cs.imageBoxShadow + '; }'+'\n';
7299 s += s1 + '.nGY2Viewer .toolbarBackground { background:' + cs.barBackground + '; }'+'\n';
7300 s += s1 + '.nGY2Viewer .nGY2ViewerToolsTopLeft { background:' + cs.barBackground + '; }'+'\n';
7301 s += s1 + '.nGY2Viewer .nGY2ViewerToolsTopRight { background:' + cs.barBackground + '; }'+'\n';
7302 s += s1 + '.nGY2Viewer .nGY2ViewerAreaNext { background:' + cs.barBackground + '; }'+'\n';
7303 s += s1 + '.nGY2Viewer .nGY2ViewerAreaPrevious { background:' + cs.barBackground + '; }'+'\n';
7304 s += s1 + '.nGY2Viewer .toolbar { border:' + cs.barBorder + '; color:' + cs.barColor + '; }'+'\n';
7305 s += s1 + '.nGY2Viewer .toolbar .previousButton:after { color:' + cs.barColor + '; }'+'\n';
7306 s += s1 + '.nGY2Viewer .toolbar .nextButton:after { color:' + cs.barColor + '; }'+'\n';
7307 s += s1 + '.nGY2Viewer .toolbar .closeButton:after { color:' + cs.barColor + '; }'+'\n';
7308 s += s1 + '.nGY2Viewer .toolbar .label .title { color:' + cs.barColor + '; }'+'\n';
7309 s += s1 + '.nGY2Viewer .toolbar .label .description { color:' + cs.barDescriptionColor + '; }'+'\n';
7310 jQuery('head').append('<style>' + s + '</style>');
7311 G.VOM.$cont.addClass(G.VOM.viewerTheme);
7312 };
7313
7314
7315
7316 /** @function SetPolyFills */
7317 function SetPolyFills() {
7318
7319 // POLYFILL FOR BIND function --> for older Safari mobile
7320 // found on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind#Compatibility
7321 if (!Function.prototype.bind) {
7322 Function.prototype.bind = function (oThis) {
7323 if (typeof this !== "function") {
7324 // closest thing possible to the ECMAScript 5
7325 // internal IsCallable function
7326 throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
7327 }
7328
7329 var aArgs = Array.prototype.slice.call(arguments, 1),
7330 fToBind = this,
7331 fNOP = function () {},
7332 fBound = function () {
7333 return fToBind.apply(this instanceof fNOP && oThis
7334 ? this
7335 : oThis,
7336 aArgs.concat(Array.prototype.slice.call(arguments)));
7337 };
7338
7339 fNOP.prototype = this.prototype;
7340 fBound.prototype = new fNOP();
7341
7342 return fBound;
7343 };
7344 }
7345
7346 // requestAnimationFrame polyfill by Erik M�ller. fixes from Paul Irish and Tino Zijdel
7347 // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
7348 // http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
7349 // MIT license
7350 (function() {
7351 var lastTime = 0;
7352 var vendors = ['ms', 'moz', 'webkit', 'o'];
7353 for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
7354 window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
7355 window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
7356 }
7357 if (!window.requestAnimationFrame)
7358 window.requestAnimationFrame = function(callback, element) {
7359 var currTime = new Date().getTime();
7360 var timeToCall = Math.max(0, 16 - (currTime - lastTime));
7361 var id = window.setTimeout(function() { callback(currTime + timeToCall); }, timeToCall);
7362 lastTime = currTime + timeToCall;
7363 return id;
7364 };
7365
7366 if (!window.cancelAnimationFrame)
7367 window.cancelAnimationFrame = function(id) {
7368 clearTimeout(id);
7369 };
7370 }());
7371
7372 // array.removeIf -> removes items from array base on a function's result
7373 Array.prototype.removeIf = function(callback) {
7374 var i = this.length;
7375 while (i--) {
7376 if (callback(this[i], i)) {
7377 this.splice(i, 1);
7378 }
7379 }
7380 };
7381
7382 // IE11 for startsWith
7383 // thanks to @lichtamberg - https://github.com/lichtamberg
7384 if (!String.prototype.startsWith) {
7385 String.prototype.startsWith = function(searchString, position) {
7386 position = position || 0;
7387 return this.indexOf(searchString, position) === position;
7388 };
7389 }
7390
7391 }
7392
7393
7394 // Gallery clicked/touched -> retrieve & execute action
7395 function GalleryClicked(e) {
7396
7397 var r = GalleryEventRetrieveElementl(e, false);
7398
7399 if( r.GOMidx == -1 ) { return 'exit'; }
7400
7401 var idx = G.GOM.items[r.GOMidx].thumbnailIdx;
7402 if( G.GOM.slider.hostIdx == r.GOMidx ) {
7403 // slider on thumbnail -> open the displayed image
7404 idx = G.GOM.items[G.GOM.slider.currentIdx].thumbnailIdx;
7405 }
7406 switch( r.action ) {
7407 case 'OPEN':
7408 ThumbnailOpen(idx, false);
7409 return 'exit';
7410 break;
7411 case 'DISPLAY':
7412 // used the display icon (ignore if selection mode)
7413 ThumbnailOpen(idx, true);
7414 return 'exit';
7415 break;
7416 case 'TOGGLESELECT':
7417 ThumbnailSelectionToggle(idx);
7418 return 'exit';
7419 break;
7420 case 'SHARE':
7421 PopupShare(idx);
7422 return 'exit';
7423 break;
7424 case 'DOWNLOAD':
7425 DownloadImage(idx);
7426 return 'exit';
7427 break;
7428 case 'INFO':
7429 ItemDisplayInfo(G.I[idx]);
7430 return 'exit';
7431 break;
7432 case 'CART':
7433 AddToCart(idx);
7434 return 'exit';
7435 break;
7436 default:
7437 // all other actions (custom1..10, or anything else)
7438 var fu = G.O.fnThumbnailToolCustAction;
7439 if( fu !== null ) {
7440 typeof fu == 'function' ? fu(r.action, G.I[idx]) : window[fu](r.action, G.I[idx]);
7441 }
7442 break;
7443 }
7444 }
7445
7446 // Download an image
7447 function DownloadImage(idx) {
7448 if( G.I[idx].mediaKind != 'img' ) { return; }
7449
7450
7451 var url = G.I[idx].src;
7452
7453 if( G.I[idx].downloadURL != undefined && G.I[idx].downloadURL != '' ) {
7454 url = G.I[idx].downloadURL;
7455 }
7456
7457 var a = document.createElement('a');
7458 a.href = url;
7459 // a.download = url.split('.').pop();
7460 a.download = url.split('/').pop();
7461 a.target = '_blank';
7462 a.style.display = 'none';
7463 document.body.appendChild(a);
7464 a.click();
7465 document.body.removeChild(a);
7466
7467 }
7468
7469 // add one image to the shopping cart
7470 function AddToCart( idx ) {
7471 // increment counter if already in shopping cart
7472 var found=false;
7473 for( var i=0; i<G.shoppingCart.length; i++ ) {
7474 if( G.shoppingCart[i].idx == idx ) {
7475 G.shoppingCart[i].cnt++;
7476 var fu = G.O.fnShoppingCartUpdated;
7477 if( fu !== null ) {
7478 typeof fu == 'function' ? fu(G.shoppingCart, G.I[idx]) : window[fu](G.shoppingCart, G.I[idx]);
7479 }
7480 TriggerCustomEvent('shoppingCartUpdated');
7481 return;
7482 }
7483 }
7484
7485 // add to shopping cart
7486 if( !found) {
7487 G.shoppingCart.push( { idx:idx, ID:G.I[idx].GetID(), cnt:1} );
7488 var fu=G.O.fnShoppingCartUpdated;
7489 if( fu !== null ) {
7490 typeof fu == 'function' ? fu(G.shoppingCart, G.I[idx]) : window[fu](G.shoppingCart, G.I[idx]);
7491 }
7492 TriggerCustomEvent('shoppingCartUpdated');
7493 }
7494 }
7495
7496
7497 // All thumbnails are set to unselected
7498 function ThumbnailSelectionClear() {
7499 G.GOM.nbSelected = 0;
7500 for( var i = 0, nbTn = G.GOM.items.length; i < nbTn ; i++ ) {
7501 var item = G .I[G.GOM.items[i].thumbnailIdx];
7502 if( item.selected ) {
7503 item.selected = false;
7504 var fu = G.O.fnThumbnailSelection;
7505 if( fu !== null ) {
7506 typeof fu == 'function' ? fu(item.$elt, item, G.I) : window[fu](item.$elt, item, G.I);
7507 }
7508 }
7509 item.selected = false;
7510 }
7511 }
7512
7513 function ThumbnailSelectionToggle( idx ){
7514 var item = G.I[idx];
7515 if( item.selected === true ) {
7516 ThumbnailSelectionSet(item, false);
7517 G.GOM.nbSelected--;
7518 TriggerCustomEvent('itemUnSelected');
7519 }
7520 else {
7521 ThumbnailSelectionSet(item, true);
7522 G.GOM.nbSelected++;
7523 TriggerCustomEvent('itemSelected');
7524 }
7525 }
7526
7527
7528 // this replaces ThumbnailSelection()
7529 function ThumbnailSelectionSet(item, selected ){
7530
7531 item.selected = selected;
7532
7533 ThumbnailSelectionSetIcon( item );
7534
7535 // called when the selection status of an item changed
7536 var fu=G.O.fnThumbnailSelection;
7537 if( fu !== null ) {
7538 typeof fu == 'function' ? fu(item.$elt, item, G.I) : window[fu](item.$elt, item, G.I);
7539 }
7540
7541 }
7542
7543 function ThumbnailSelectionSetIcon( item ) {
7544 if( item.$elt == null ) {
7545 // thumbnail is not built
7546 return;
7547 }
7548 // var $sub = item.$getElt('.nGY2GThumbnailSub');
7549 var $sub = item.$getElt('.nGY2GThumbnail');
7550 var $icon = item.$getElt('.nGY2GThumbnailIconImageSelect');
7551 if( item.selected === true) {
7552 $sub.addClass('nGY2GThumbnailSubSelected');
7553 $icon.addClass('nGY2ThumbnailSelected');
7554 $icon.removeClass('nGY2ThumbnailUnselected');
7555 $icon.html(G.O.icons.thumbnailSelected);
7556 }
7557 else {
7558 $sub.removeClass('nGY2GThumbnailSubSelected');
7559 $icon.removeClass('nGY2ThumbnailSelected');
7560 $icon.addClass('nGY2ThumbnailUnselected');
7561 $icon.html(G.O.icons.thumbnailUnselected);
7562 }
7563 }
7564
7565
7566 // display a modal popup for sharing image/album
7567 function PopupShare(idx) {
7568
7569 // SEE SAMPLES: https://gist.github.com/chrisjlee/5196139
7570 // https://github.com/Julienh/Sharrre
7571
7572 var item=G.I[idx];
7573
7574 var currentURL=document.location.protocol + '//' + document.location.hostname + document.location.pathname;
7575 var newLocationHash = '#nanogallery/' + G.baseEltID + '/';
7576 if( item.kind == 'image' ) {
7577 newLocationHash += item.albumID + '/' + item.GetID();
7578 }
7579 else {
7580 newLocationHash += item.GetID();
7581 }
7582
7583 var content = '';
7584 content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="facebook">' + G.O.icons.shareFacebook + '</div>';
7585 content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="pinterest">' + G.O.icons.sharePinterest + '</div>';
7586 content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="tumblr">' + G.O.icons.shareTumblr + '</div>';
7587 content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="twitter">' + G.O.icons.shareTwitter + '</div>';
7588 // content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="googleplus">' + G.O.icons.shareGooglePlus + '</div>';
7589 content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="vk">' + G.O.icons.shareVK + '</div>';
7590 content += '<div class="nGY2PopupOneItem" style="text-align:center;" data-share="mail">' + G.O.icons.shareMail + '</div>';
7591 content += '<div class="nGY2PopupOneItem" style="text-align:center;"></div>';
7592 content += '<input class="nGY2PopupOneItemText" readonly type="text" value="' + currentURL+newLocationHash + '" style="width:100%;text-align:center;">';
7593 content += '<br>';
7594
7595 currentURL = encodeURIComponent(document.location.protocol + '//' + document.location.hostname + document.location.pathname + newLocationHash);
7596
7597 var currentTitle = item.title;
7598 var currentTn = item.thumbImg().src;
7599
7600
7601 Popup('Share to:', content, 'Center');
7602
7603 G.popup.$elt.find('.nGY2PopupOneItem').on('click', function(e) {
7604 e.stopPropagation();
7605
7606 var shareURL = '';
7607 var found = true;
7608 switch(jQuery(this).attr('data-share').toUpperCase()) {
7609 case 'FACEBOOK':
7610 // <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>
7611 //window.open("https://www.facebook.com/sharer.php?u="+currentURL,"","height=368,width=600,left=100,top=100,menubar=0");
7612 shareURL = 'https://www.facebook.com/sharer.php?u=' + currentURL;
7613 break;
7614 case 'VK':
7615 shareURL = 'http://vk.com/share.php?url=' + currentURL;
7616 break;
7617 case 'GOOGLEPLUS':
7618 shareURL = "https://plus.google.com/share?url=" + currentURL;
7619 break;
7620 case 'TWITTER':
7621 // shareURL="https://twitter.com/share?url="+currentURL+"&text="+currentTitle;
7622 shareURL = 'https://twitter.com/intent/tweet?text=' + currentTitle + 'url=' + currentURL;
7623 break;
7624 case 'PINTEREST':
7625 // shareURL='https://pinterest.com/pin/create/bookmarklet/?media='+currentTn+'&url='+currentURL+'&description='+currentTitle;
7626 shareURL = 'https://pinterest.com/pin/create/button/?media=' + currentTn + '&url=' + currentURL + '&description=' + currentTitle;
7627 break;
7628 case 'TUMBLR':
7629 //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;
7630 shareURL = 'http://www.tumblr.com/share/link?url=' + currentURL + '&name=' + currentTitle;
7631 break;
7632 case 'MAIL':
7633 shareURL = 'mailto:?subject=' + currentTitle + '&body=' + currentURL;
7634 break;
7635 default:
7636 found = false;
7637 break;
7638 }
7639
7640 if( found ) {
7641 window.open(shareURL, "" , "height=550,width=500,left=100,top=100,menubar=0" );
7642 G.popup.close();
7643 // $popup.remove();
7644 }
7645
7646 });
7647 }
7648
7649 // build a modal popup
7650 function Popup(title, content, align) {
7651 var pp = '<div class="nGY2Popup" style="opacity:0;"><div class="nGY2PopupContent' + align + '">';
7652 pp += '<div class="nGY2PopupCloseButton">' + G.O.icons.buttonClose + '</div>';
7653 pp += '<div class="nGY2PopupTitle">' + title + '</div>';
7654 pp += content;
7655 pp += '</div></div>';
7656
7657 G.popup.$elt = jQuery(pp).appendTo('body');
7658 setElementOnTop( G.VOM.$viewer, G.popup.$elt);
7659
7660 G.popup.isDisplayed = true;
7661
7662 var tweenable = new NGTweenable();
7663 tweenable.tween({
7664 from: { opacity: 0 },
7665 to: { opacity: 1 },
7666 easing: 'easeInOutSine',
7667 duration: 250,
7668 step: function (state, att) {
7669 G.popup.$elt.css( state );
7670 }
7671 });
7672
7673 G.popup.$elt.find('.nGY2PopupCloseButton').on('click', function(e) {
7674 e.stopPropagation();
7675 G.popup.close();
7676 });
7677
7678 }
7679
7680
7681 function GalleryMouseEnter(e) {
7682 if( !G.VOM.viewerDisplayed && G.GOM.albumIdx != -1 ) {
7683 var r = GalleryEventRetrieveElementl(e, true);
7684 // if( r.action == 'OPEN' && r.GOMidx != -1 ) {
7685 if( r.GOMidx != -1 ) {
7686 var target = e.target || e.srcElement;
7687 // if( target.getAttribute('class') != 'nGY2GThumbnail' ) { return; }
7688 ThumbnailHover(r.GOMidx);
7689 }
7690 }
7691 }
7692
7693 function GalleryMouseLeave(e) {
7694 if( !G.VOM.viewerDisplayed && G.GOM.albumIdx != -1 ) {
7695 var r = GalleryEventRetrieveElementl(e, true);
7696 if( r.GOMidx != -1 ) {
7697 var target = e.target || e.srcElement;
7698 // if( target.getAttribute('class') != 'nGY2GThumbnail' ) { return; }
7699 ThumbnailHoverOut(r.GOMidx);
7700 }
7701 }
7702 }
7703
7704 function GalleryEventRetrieveElementl( e, ignoreSubItems ) {
7705 var r = { action: 'NONE', GOMidx: -1 };
7706
7707 if( e == undefined ) {
7708 return r;
7709 }
7710 var target = e.target || e.srcElement;
7711 while( target != G.$E.conTnParent[0] ) { // loop element parent up to find the thumbnail element
7712 if( jQuery(target).hasClass('nGY2GThumbnail') ) {
7713 if( r.action == 'NONE' ) {
7714 r.action = 'OPEN';
7715 }
7716 r.GOMidx = jQuery(target).data('index');
7717 return r;
7718 }
7719 // if( !ignoreSubItems && jQuery(target).hasClass('nGY2GThumbnailIcon') ) {
7720 if( !ignoreSubItems ) {
7721 var a = jQuery(target).data('ngy2action');
7722 if( a != '' && a != undefined ) {
7723 r.action = a;
7724 }
7725 }
7726 if( target.parentNode == null ) {
7727 return r;
7728 }
7729 target = target.parentNode;
7730 }
7731 return r;
7732 }
7733
7734
7735 // Open one thumbnail
7736 function ThumbnailOpen( idx, ignoreSelected ) {
7737 var item = G.I[idx];
7738
7739 var fu = G.O.fnThumbnailClicked;
7740 if( fu !== null ) {
7741 typeof fu == 'function' ? fu(item.$elt, item) : window[fu](item.$elt, item);
7742 }
7743
7744 // open URL
7745 if( item.destinationURL !== undefined && item.destinationURL.length > 0 ) {
7746 window.location = item.destinationURL;
7747 return;
7748 }
7749
7750 switch( item.kind ) {
7751 case 'image':
7752 if( ignoreSelected === false && G.GOM.nbSelected > 0 ) {
7753 ThumbnailSelectionToggle(idx);
7754 }
7755 else {
7756 // display image
7757 DisplayPhotoIdx(idx);
7758 }
7759 break;
7760 case 'album':
7761 if( ignoreSelected === false && G.GOM.nbSelected > 0 ) {
7762 ThumbnailSelectionToggle(idx);
7763 }
7764 else {
7765 if( G.O.thumbnailAlbumDisplayImage && idx != 0 ) {
7766 // display album content in lightbox
7767 DisplayFirstMediaInAlbum(idx);
7768 return;
7769 }
7770 else {
7771 // display album content in gallery
7772 DisplayAlbum('-1', item.GetID());
7773 }
7774 }
7775 break;
7776 case 'albumUp':
7777 var parent = NGY2Item.Get(G, item.albumID);
7778 DisplayAlbum('-1', parent.albumID);
7779 break;
7780 }
7781 }
7782
7783 function DisplayFirstMediaInAlbum( albumIdx ) {
7784 if( G.O.debugMode ) { console.log('#DisplayFirstPhotoInAlbum : '+ albumIdx); }
7785
7786 var item = G.I[albumIdx];
7787
7788 var l = G.I.length;
7789 for( var i = 0; i < l; i++ ) {
7790 if( G.I[i].albumID == item.GetID() ) {
7791 DisplayPhotoIdx( i );
7792 return;
7793 }
7794 }
7795
7796 // load album content
7797 AlbumGetContent( item.GetID(), DisplayFirstMediaInAlbum, albumIdx, null );
7798
7799 }
7800
7801
7802 // Open link to original image (new window)
7803 function OpenOriginal( item ) {
7804 switch( G.O.kind ) {
7805 case 'flickr':
7806 var sU = 'https://www.flickr.com/photos/' + G.O.userID + '/' + item.GetID();
7807 if( item.albumID != '0' ) {
7808 sU += '/in/album-' + item.albumID + '/';
7809 }
7810 window.open(sU, '_blank');
7811 break;
7812 case 'picasa':
7813 case 'google':
7814 case 'google2':
7815 // no more working since Google changed the access to Google Photos in 2017
7816 // var sU='https://plus.google.com/photos/'+G.O.userID+'/albums/'+item.albumID+'/'+item.GetID();
7817 // window.open(sU,'_blank');
7818 // break;
7819 default:
7820 var sU = item.responsiveURL();
7821 window.open(sU, '_blank');
7822 break;
7823 }
7824 }
7825
7826 // Display one photo (with internal or external viewer)
7827 function DisplayPhotoIdx( ngy2ItemIdx ) {
7828
7829 if( !G.O.thumbnailOpenImage ) { return; }
7830
7831 if( G.O.thumbnailOpenOriginal ) {
7832 // Open link to original image
7833 OpenOriginal( G.I[ngy2ItemIdx] );
7834 return;
7835 }
7836
7837 var items = [];
7838 G.VOM.currItemIdx = 0;
7839 G.VOM.items = [];
7840 G.VOM.albumID = G.I[ngy2ItemIdx].albumID;
7841
7842 var vimg = new VImg(ngy2ItemIdx);
7843 G.VOM.items.push(vimg);
7844 items.push(G.I[ngy2ItemIdx]);
7845 //TODO -> danger? -> pourquoi reconstruire la liste si d�j� ouvert (back/forward)
7846 var l = G.I.length;
7847 for( var idx = ngy2ItemIdx+1; idx < l ; idx++) {
7848 var item = G.I[idx];
7849 if( item.kind == 'image' && item.isToDisplay(G.VOM.albumID) && item.destinationURL == '' ) {
7850 var vimg = new VImg(idx);
7851 G.VOM.items.push(vimg);
7852 items.push(item);
7853 }
7854 }
7855 var last = G.VOM.items.length;
7856 var cnt = 1;
7857 for( var idx = 0; idx < ngy2ItemIdx ; idx++) {
7858 var item = G.I[idx];
7859 if( item.kind == 'image' && item.isToDisplay(G.VOM.albumID) && item.destinationURL == '' ) {
7860 var vimg = new VImg(idx);
7861 vimg.mediaNumber = cnt;
7862 G.VOM.items.push(vimg);
7863 items.push(item);
7864 cnt++;
7865 }
7866 }
7867 for( var i = 0; i < last; i++ ) {
7868 G.VOM.items[i].mediaNumber = cnt;
7869 cnt++;
7870 }
7871
7872 // opens media with external viewer
7873 var fu = G.O.fnThumbnailOpen;
7874 if( fu !== null ) {
7875 typeof fu == 'function' ? fu(items) : window[fu](items);
7876 return;
7877 }
7878
7879 // use internal viewer
7880 if( !G.VOM.viewerDisplayed ) {
7881 // build viewer and display
7882 OpenInternalViewer();
7883 }
7884 else {
7885 // viewer already displayed -> display new media in current viewer
7886 G.VOM.$mediaCurrent.empty();
7887 var item = G.VOM.NGY2Item(0);
7888 var spreloader = '<div class="nGY2ViewerMediaLoaderDisplayed"></div>';
7889 if( item.mediaKind == 'img' && item.imageWidth != 0 && item.imageHeight != 0 ) {
7890 spreloader = '<div class="nGY2ViewerMediaLoaderHidden"></div>';
7891 }
7892 G.VOM.$mediaCurrent.append( spreloader + item.mediaMarkup);
7893 ViewerSetMediaVisibility(G.VOM.NGY2Item(1), G.VOM.$mediaNext, 0);
7894 ViewerSetMediaVisibility(G.VOM.NGY2Item(-1), G.VOM.$mediaPrevious, 0);
7895 if( item.mediaKind == 'img' ) {
7896 G.VOM.ImageLoader.loadImage(VieweImgSizeRetrieved, item);
7897 }
7898 // G.VOM.$mediaCurrent.css({ opacity:0 }).attr('src','');
7899 // G.VOM.ImageLoader.loadImage(VieweImgSizeRetrieved, G.VOM.NGY2Item(0));
7900 // G.VOM.$mediaCurrent.children().eq(0).attr('src',G.emptyGif).attr('src', G.VOM.NGY2Item(0).responsiveURL());
7901 DisplayInternalViewer(0, '');
7902 }
7903 }
7904
7905 function ViewerZoomStart() {
7906 if( G.O.viewerZoom && !G.VOM.viewerMediaIsChanged ) {
7907 var item=G.VOM.NGY2Item(0);
7908 if( item.imageHeight > 0 && item.imageWidth > 0 ) {
7909 if( G.VOM.zoom.isZooming === false ) {
7910 // default zoom
7911 G.VOM.zoom.userFactor = 1;
7912 G.VOM.zoom.isZooming = true;
7913 }
7914 return true;
7915 }
7916 }
7917 }
7918
7919 function ViewerZoomIn( zoomIn ) {
7920 if( zoomIn ) {
7921 // zoom in
7922 G.VOM.zoom.userFactor+=0.1;
7923 ViewerZoomMax();
7924 }
7925 else {
7926 // zoom out
7927 G.VOM.zoom.userFactor-=0.1;
7928 ViewerZoomMin();
7929 }
7930 ViewerMediaSetPosAndZoom();
7931 }
7932
7933 function ViewerZoomMax() {
7934 if( G.VOM.zoom.userFactor > 3 ) {
7935 G.VOM.zoom.userFactor = 3;
7936 }
7937 }
7938 function ViewerZoomMin() {
7939
7940 if( G.VOM.zoom.userFactor < 0.2 ) {
7941 G.VOM.zoom.userFactor = 0.2;
7942 }
7943 }
7944
7945
7946
7947 // Set position and size of all 3 media containers
7948 function ViewerMediaSetPosAndZoom() {
7949
7950 if( !G.VOM.zoom.isZooming ) {
7951 G.VOM.zoom.userFactor = 1;
7952 }
7953 // window.ng_draf( function() {
7954 ViewerMediaSetPosAndZoomOne( G.VOM.NGY2Item(0), G.VOM.$mediaCurrent, true );
7955 ViewerMediaSetPosAndZoomOne( G.VOM.NGY2Item(-1), G.VOM.$mediaPrevious, false );
7956 ViewerMediaSetPosAndZoomOne( G.VOM.NGY2Item(1), G.VOM.$mediaNext, false );
7957 // });
7958 }
7959
7960
7961
7962 // Media which is not IMG -> center and set size
7963 function ViewerMediaCenterNotImg( $mediaContainer ) {
7964 var $media = $mediaContainer.children().eq(1);
7965 $media.css( {'height': '80%' });
7966 $media.css( {'width': '90%' });
7967 $media[0].style[G.CSStransformName] = 'translate(0px, "50%") ';
7968 }
7969
7970 // Set position and size of ONE media container
7971 function ViewerMediaSetPosAndZoomOne(item, $img, isCurrent ) {
7972
7973 if( item.mediaKind != 'img' ) {
7974 ViewerMediaCenterNotImg($img);
7975 return;
7976 }
7977
7978 if( item.imageHeight == 0 || item.imageWidth == 0 ) {
7979 ViewerSetMediaVisibility( item, $img, 0 );
7980 return;
7981 }
7982
7983 // part 1: set the image size
7984 var zoomUserFactor = isCurrent == true ? G.VOM.zoom.userFactor : 1;
7985
7986 var dpr = 1;
7987 if( G.O.viewerImageDisplay == 'bestImageQuality' ) {
7988 dpr = window.devicePixelRatio;
7989 }
7990
7991 // retrieve the base zoom factor (image fill screen)
7992 var zoomBaseFactorW = (G.VOM.window.lastWidth - G.VOM.padding.V) / (item.imageWidth / dpr);
7993 var zoomBaseFactorH = (G.VOM.window.lastHeight - G.VOM.padding.H) / (item.imageHeight / dpr);
7994 var zoomBaseFactor = Math.min(zoomBaseFactorW, zoomBaseFactorH);
7995 if( zoomBaseFactor > 1 && G.O.viewerImageDisplay != 'upscale' ) {
7996 // no upscale
7997 zoomBaseFactor = 1;
7998 }
7999
8000 var imageCurrentHeight = (item.imageHeight / dpr) * zoomUserFactor * zoomBaseFactor;
8001 var imageCurrentWidth = (item.imageWidth / dpr) * zoomUserFactor * zoomBaseFactor;
8002 $img.children().eq(1).css( {'height': imageCurrentHeight });
8003 $img.children().eq(1).css( {'width': imageCurrentWidth });
8004
8005 // retrieve posX/Y to center image
8006 var posX = 0;
8007 if( imageCurrentWidth > G.VOM.window.lastWidth ) {
8008 posX = -(imageCurrentWidth - G.VOM.window.lastWidth)/2;
8009 }
8010 var posY = 0;
8011 if( imageCurrentHeight > G.VOM.window.lastHeight ) {
8012 posY = ( imageCurrentHeight - G.VOM.window.lastHeight ) / 2;
8013 }
8014 posY = 0; // actually, it seems that the image is always centered vertically -> so no need to to anything
8015
8016 // Part 2: set the X/Y position (for zoom/pan)
8017 if( isCurrent ) {
8018 if( !G.VOM.zoom.isZooming ) {
8019 G.VOM.panPosX = 0;
8020 G.VOM.panPosY = 0;
8021 }
8022 G.VOM.zoom.posX = posX;
8023 G.VOM.zoom.posY = posY;
8024 ViewerImagePanSetPosition(G.VOM.panPosX, G.VOM.panPosY, $img, false);
8025 }
8026 // else {
8027 //$img[0].style[G.CSStransformName]= 'translate3D('+ posX+'px, '+ posY+'px, 0) ';
8028 // }
8029 else {
8030 // set the pan position of each media container
8031 ViewerMediaPanX( G.VOM.swipePosX );
8032 $img.children().eq(1)[0].style[G.CSStransformName]= 'translate(0px, 0px) rotate('+ item.rotationAngle +'deg)';
8033 }
8034
8035 }
8036
8037 // position the image depending on the zoom factor and the pan X/Y position
8038 // IMG is the only media supporting zoom/pan
8039 function ViewerImagePanSetPosition(posX, posY, imageContainer, savePosition ) {
8040 if( savePosition ) {
8041 G.VOM.panPosX = posX;
8042 G.VOM.panPosY = posY;
8043 }
8044
8045 posX += G.VOM.zoom.posX;
8046 posY += G.VOM.zoom.posY;
8047
8048 // imageContainer.children().eq(1)[0].style[G.CSStransformName]= 'translate('+ posX + 'px, '+ posY + 'px)';
8049 imageContainer.children().eq(1)[0].style[G.CSStransformName]= 'translate('+ posX + 'px, '+ posY + 'px) rotate('+ G.VOM.NGY2Item(0).rotationAngle +'deg)';
8050
8051
8052 }
8053
8054
8055 // display media with internal viewer
8056 function OpenInternalViewer( ) {
8057
8058 G.VOM.viewerDisplayed = true;
8059 G.GOM.firstDisplay = false;
8060
8061 G.VOM.saveOverflowX = window.getComputedStyle(document.body)['overflow-x'];
8062 G.VOM.saveOverflowY = window.getComputedStyle(document.body)['overflow-y'];
8063 jQuery('body').css({ overflow: 'hidden' }); //avoid scrollbars
8064
8065 G.VOM.$cont = jQuery('<div class="nGY2 nGY2ViewerContainer" style="opacity:1"></div>').appendTo('body');
8066
8067 SetViewerTheme();
8068
8069 G.VOM.$viewer = jQuery('<div class="nGY2Viewer" style="opacity:0" itemscope itemtype="http://schema.org/ImageObject"></div>').appendTo( G.VOM.$cont );
8070 G.VOM.$viewer.css({ msTouchAction: 'none', touchAction: 'none' }); // avoid pinch zoom
8071
8072 G.VOM.currItemIdx = 0;
8073 var sMedia = '<div class="nGY2ViewerMediaPan"><div class="nGY2ViewerMediaLoaderDisplayed"></div>' + G.VOM.NGY2Item(-1).mediaMarkup + '</div>'; // previous media
8074 sMedia += '<div class="nGY2ViewerMediaPan"><div class="nGY2ViewerMediaLoaderDisplayed"></div>' + G.VOM.NGY2Item(0).mediaMarkup + '</div>'; // current media
8075 sMedia += '<div class="nGY2ViewerMediaPan"><div class="nGY2ViewerMediaLoaderDisplayed"></div>' + G.VOM.NGY2Item(1).mediaMarkup + '</div>'; // next media
8076
8077 var sNav = '';
8078 var iconP = G.O.icons.viewerImgPrevious;
8079 if( iconP != undefined && iconP != '') {
8080 sNav += '<div class="nGY2ViewerAreaPrevious ngy2viewerToolAction" data-ngy2action="previous">' + iconP + '</div>';
8081 }
8082 var iconN = G.O.icons.viewerImgNext;
8083 if( iconN != undefined && iconN != '') {
8084 sNav += '<div class="nGY2ViewerAreaNext ngy2viewerToolAction" data-ngy2action="next">' + iconN + '</div>';
8085 }
8086
8087 G.VOM.$content = jQuery('<div class="nGY2ViewerContent">' + sMedia + sNav + '</div>').appendTo( G.VOM.$viewer );
8088
8089 var $mediaPan = G.VOM.$content.find('.nGY2ViewerMediaPan');
8090 G.VOM.$mediaPrevious = $mediaPan.eq(0); // pointer to previous media container
8091 G.VOM.$mediaCurrent = $mediaPan.eq(1); // pointer to current media container
8092 G.VOM.$mediaNext = $mediaPan.eq(2); // pointer to next media container
8093
8094 G.VOM.ImageLoader.loadImage( VieweImgSizeRetrieved, G.VOM.NGY2Item(0) );
8095 G.VOM.ImageLoader.loadImage( VieweImgSizeRetrieved, G.VOM.NGY2Item(-1) );
8096 G.VOM.ImageLoader.loadImage( VieweImgSizeRetrieved, G.VOM.NGY2Item(1) );
8097
8098 G.VOM.padding.H = parseInt(G.VOM.$content.css("padding-left")) + parseInt(G.VOM.$content.css("padding-right"));
8099 G.VOM.padding.V = parseInt(G.VOM.$content.css("padding-top")) + parseInt(G.VOM.$content.css("padding-bottom"));
8100
8101 // build media toolbar container
8102 var vtbBg1 = '';
8103 var vtbBg2 = ' toolbarBackground';
8104 if( G.O.viewerToolbar.fullWidth ) {
8105 vtbBg1 = ' toolbarBackground';
8106 vtbBg2 = '';
8107 }
8108 var vtbAlign = 'text-align:center;';
8109 switch ( G.O.viewerToolbar.align ) {
8110 case 'left':
8111 vtbAlign = 'text-align:left;';
8112 break;
8113 case 'right':
8114 vtbAlign = 'text-align:right;';
8115 break;
8116 }
8117 var sTB = '<div class="toolbarContainer nGEvent' + vtbBg1 + '" style="visibility:' +(G.O.viewerToolbar.display ? "visible" : "hidden")+';'+vtbAlign+'"><div class="toolbar nGEvent' + vtbBg2 + '"></div></div>';
8118 G.VOM.$toolbar = jQuery(sTB).appendTo(G.VOM.$viewer);
8119
8120 if( G.VOM.toolbarMode == 'min' || (G.O.viewerToolbar.autoMinimize > 0 && G.O.viewerToolbar.autoMinimize >= G.GOM.cache.viewport.w) ) {
8121 ViewerToolbarForVisibilityMin();
8122 }
8123 else {
8124 ViewerToolbarForVisibilityStd();
8125 }
8126
8127 // top-left toolbar
8128 if( G.O.viewerTools.topLeft != '' ) {
8129 var sTopLeft = '<div class="nGY2ViewerToolsTopLeft nGEvent"><div class="toolbar nGEvent">';
8130 var sTL = G.O.viewerTools.topLeft.split(',');
8131 for( var i = 0, sTLL = sTL.length; i < sTLL; i++) {
8132 sTopLeft += ToolbarAddElt( sTL[i] );
8133 }
8134 sTopLeft += '</div></div>';
8135 G.VOM.$toolbarTL = jQuery(sTopLeft).appendTo(G.VOM.$viewer);
8136 }
8137 // top-right toolbar
8138 if( G.O.viewerTools.topRight != '' ) {
8139 var sTopRight = '<div class="nGY2ViewerToolsTopRight nGEvent"><div class="toolbar nGEvent">';
8140 var sTR = G.O.viewerTools.topRight.split(',');
8141 for( var i = 0, sTRL = sTR.length; i < sTRL; i++) {
8142 sTopRight += ToolbarAddElt( sTR[i] );
8143 }
8144 sTopRight += '</div></div>';
8145 G.VOM.$toolbarTR = jQuery(sTopRight).appendTo(G.VOM.$viewer);
8146 }
8147
8148 // set the events handler for toolbars
8149 ViewerToolsOn();
8150
8151 // display logo
8152 if( G.O.viewerDisplayLogo ) { G.$E.vwLogo=jQuery('<div class="nGY2 nGY2ViewerLogo"></div>').appendTo(G.VOM.$viewer); }
8153
8154 // Go to fullscreen mode
8155 if( ngscreenfull.enabled && G.O.viewerFullscreen ) { ngscreenfull.request(); }
8156
8157 setElementOnTop('', G.VOM.$viewer);
8158 ResizeInternalViewer(true);
8159 G.VOM.timeImgChanged = new Date().getTime();
8160
8161 // viewer display transition
8162 var tweenable = new NGTweenable();
8163 tweenable.tween({
8164 from: { opacity: 0, posY: G.VOM.window.lastHeight*.5 },
8165 to: { opacity: 1, posY: 0 },
8166 delay: 30,
8167 duration: 500,
8168 easing: 'easeOutQuart',
8169 step: function (state) {
8170 G.VOM.$viewer.css('opacity', state.opacity);
8171 G.VOM.$viewer[0].style[G.CSStransformName] = 'translateY(' + (state.posY) + 'px) ';
8172 }
8173 });
8174
8175 // stop click propagation on media ==> if the user clicks outside of an media, the viewer is closed
8176 // --> no more supported since v2.0.0
8177 // G.VOM.$viewer.find('img').on('click', function (e) { e.stopPropagation(); });
8178
8179
8180 ViewerMediaPanX(0);
8181 ViewerSetEvents();
8182
8183 DisplayInternalViewer(0, '');
8184
8185 if( G.O.slideshowAutoStart ) {
8186 G.VOM.playSlideshow = false;
8187 SlideshowToggle();
8188 }
8189 }
8190
8191 function ViewerEvents() {
8192 if( !G.VOM.viewerDisplayed || G.VOM.viewerMediaIsChanged || G.VOM.NGY2Item(0).mediaKind != 'img') {
8193 // ignore fired event if viewer not displayed or if currently changed or if current media not an image
8194 return false;
8195 }
8196 return true;
8197 }
8198
8199
8200 // viewer gesture handling
8201 function ViewerSetEvents() {
8202
8203 if( G.VOM.hammertime == null ) {
8204
8205 G.VOM.hammertime = new NGHammer.Manager(G.VOM.$cont[0], {
8206 recognizers: [
8207 [NGHammer.Pinch, { enable: true }],
8208 [NGHammer.Pan, { direction: NGHammer.DIRECTION_ALL }]
8209 ]
8210 });
8211
8212 // PAN
8213 G.VOM.hammertime.on('pan', function(ev) {
8214 if( !ViewerEvents() ) { return; }
8215
8216 if( G.VOM.zoom.isZooming ) {
8217 // pan zoomed image
8218 ViewerImagePanSetPosition(G.VOM.panPosX + ev.deltaX, G.VOM.panPosY + ev.deltaY, G.VOM.$mediaCurrent, false);
8219 if( G.VOM.toolbarsDisplayed == true ) {
8220 G.VOM.toolsHide();
8221 }
8222 }
8223 else {
8224 if( ev.deltaY > G.VOM.panThreshold && Math.abs(ev.deltaX) < G.VOM.panThreshold && !G.VOM.panXOnly ) {
8225 // pan viewer down
8226 ViewerMediaPanX( 0 );
8227 var dist = Math.min(ev.deltaY, 200);
8228 G.VOM.$viewer[0].style[G.CSStransformName] = 'translateY(' + dist + 'px) ';
8229 G.VOM.$viewer.css('opacity', 1-dist/200/2);
8230 }
8231 else {
8232 // pan media left/right
8233 if( Math.abs(ev.deltaX) > G.VOM.panThreshold ) {
8234 G.VOM.panXOnly = true;
8235 }
8236 ViewerMediaPanX( ev.deltaX );
8237 G.VOM.$viewer[0].style[G.CSStransformName] = 'translateY(0px)';
8238 G.VOM.$viewer.css('opacity', 1);
8239 }
8240 }
8241 });
8242
8243 // PAN END
8244 G.VOM.hammertime.on('panend', function(ev) {
8245 if( !ViewerEvents() ) { return; }
8246 if( G.VOM.zoom.isZooming ) {
8247 G.VOM.timeImgChanged = new Date().getTime();
8248 ViewerImagePanSetPosition(G.VOM.panPosX+ev.deltaX, G.VOM.panPosY+ev.deltaY, G.VOM.$mediaCurrent, true);
8249 }
8250 else {
8251 var panY = false;
8252 if( !G.VOM.panXOnly ) {
8253 if( ev.deltaY > 50 && Math.abs(ev.deltaX) < 50 ) {
8254 // close viewer
8255 CloseInternalViewer(G.VOM.currItemIdx);
8256 panY = true;
8257 }
8258 }
8259 if( !panY ) {
8260 if( Math.abs( ev.deltaX ) < 50 ) {
8261 ViewerMediaPanX(0);
8262 }
8263 else {
8264 ev.deltaX > 50 ? DisplayPreviousMedia() : DisplayNextMedia();
8265 }
8266 }
8267 }
8268 G.VOM.panXOnly = false;
8269 });
8270
8271 if( G.O.viewerZoom ) {
8272
8273 G.VOM.hammertime.add( new NGHammer.Tap({ event: 'doubletap', taps: 2 }) );
8274 G.VOM.hammertime.add( new NGHammer.Tap({ event: 'singletap' }) );
8275 G.VOM.hammertime.get('doubletap').recognizeWith('singletap');
8276 G.VOM.hammertime.get('singletap').requireFailure('doubletap');
8277
8278 // single tap -> next/previous media
8279 G.VOM.hammertime.on('singletap', function(ev) {
8280 if( !ViewerEvents() ) { return; }
8281 StopPropagationPreventDefault(ev.srcEvent);
8282 if( G.VOM.toolbarsDisplayed == false ) {
8283 debounce( ViewerToolsUnHide, 100, false)();
8284 }
8285 else {
8286 if( ev.target.className.indexOf('nGY2ViewerMedia') !== -1 ) {
8287 if( ev.srcEvent.pageX < (G.GOM.cache.viewport.w/2) ) {
8288 DisplayPreviousMedia();
8289 }
8290 else {
8291 DisplayNextMedia();
8292 }
8293 }
8294 }
8295 });
8296
8297 // double tap -> zoom
8298 G.VOM.hammertime.on('doubletap', function(ev) {
8299 if( !ViewerEvents() ) { return; }
8300 StopPropagationPreventDefault(ev.srcEvent);
8301
8302 if( ev.target.className.indexOf('nGY2ViewerMedia') !== -1 ) {
8303 // double tap only on image
8304 if( G.VOM.zoom.isZooming ) {
8305 G.VOM.zoom.isZooming = false;
8306 // G.VOM.zoom.userFactor = 1;
8307 ResizeInternalViewer(true);
8308 }
8309 else {
8310 if( ViewerZoomStart() ) {
8311 G.VOM.zoom.userFactor = 1.5;
8312 ViewerMediaSetPosAndZoom();
8313 }
8314 }
8315 }
8316 });
8317
8318 // pinch end
8319 G.VOM.hammertime.on('pinchend', function(ev) {
8320 ev.srcEvent.stopPropagation();
8321 ev.srcEvent.preventDefault(); // cancel mouseenter event
8322 G.VOM.timeImgChanged = new Date().getTime();
8323 });
8324 G.VOM.hammertime.on('pinch', function(ev) {
8325 ev.srcEvent.stopPropagation();
8326 ev.srcEvent.preventDefault(); // cancel mouseenter event
8327
8328 if( ViewerZoomStart() ) {
8329 G.VOM.zoom.userFactor = ev.scale;
8330 ViewerZoomMax();
8331 ViewerZoomMin();
8332 ViewerMediaSetPosAndZoom(); // center media
8333 }
8334 });
8335 }
8336 else {
8337 // no zoom -> click/tap on image to go to next/previous one
8338 G.VOM.hammertime.on('tap', function(ev) {
8339 if( !ViewerEvents() ) { return; }
8340 StopPropagationPreventDefault(ev.srcEvent);
8341 if( G.VOM.toolbarsDisplayed == false ){
8342 // display tools on tap if hidden
8343 debounce( ViewerToolsUnHide, 100, false)();
8344 }
8345 else {
8346 // display next/previous image if tools not hidden
8347 if( ev.target.className.indexOf('nGY2ViewerMedia') !== -1 ) {
8348 if( ev.srcEvent.pageX < (G.GOM.cache.viewport.w/2) ) {
8349 DisplayPreviousMedia();
8350 }
8351 else {
8352 DisplayNextMedia();
8353 }
8354 }
8355 }
8356
8357 });
8358 }
8359 }
8360 }
8361
8362 function StopPropagationPreventDefault(e) {
8363 e.stopPropagation();
8364 e.preventDefault();
8365 }
8366
8367 // Hide toolbars on user inactivity
8368 function ViewerToolsHide() {
8369 if( G.VOM.viewerDisplayed ) {
8370 G.VOM.toolbarsDisplayed = false;
8371 ViewerToolsOpacity(0);
8372 }
8373 }
8374
8375 function ViewerToolsUnHide() {
8376 if( G.VOM.viewerDisplayed ) {
8377 G.VOM.toolbarsDisplayed = true;
8378 ViewerToolsOpacity(1);
8379 G.VOM.toolsHide();
8380 }
8381 }
8382
8383 function ViewerToolsOpacity( op ) {
8384 if( G.VOM.$toolbar != null ) {
8385 G.VOM.$toolbar.css('opacity', op);
8386 }
8387 if( G.VOM.$toolbarTL != null ) {
8388 G.VOM.$toolbarTL.css('opacity', op);
8389 }
8390 if( G.VOM.$toolbarTR != null ) {
8391 G.VOM.$toolbarTR.css('opacity', op);
8392 }
8393 G.VOM.$content.find('.nGY2ViewerAreaNext').css('opacity', op);
8394 G.VOM.$content.find('.nGY2ViewerAreaPrevious').css('opacity', op);
8395 }
8396
8397
8398
8399 function ViewerToolsOn() {
8400 // removes all events
8401 G.VOM.$viewer.off('touchstart click', '.ngy2viewerToolAction', ViewerToolsAction);
8402
8403 // action button
8404 G.VOM.$viewer.on('touchstart click', '.ngy2viewerToolAction', ViewerToolsAction);
8405 }
8406
8407
8408 // Actions of the buttton/elements
8409 function ViewerToolsAction(e) {
8410 // delay to avoid twice handling on smartphone/tablet (both touchstart click events are fired)
8411 if( (new Date().getTime()) - G.timeLastTouchStart < 300 ) { return; }
8412 G.timeLastTouchStart = new Date().getTime();
8413
8414 var $this = $(this);
8415 var ngy2action = $this.data('ngy2action');
8416 if( ngy2action == undefined ) { return; }
8417 switch( ngy2action ) {
8418 case 'next':
8419 StopPropagationPreventDefault(e);
8420 DisplayNextMedia();
8421 break;
8422 case 'previous':
8423 StopPropagationPreventDefault(e);
8424 DisplayPreviousMedia();
8425 break;
8426 case 'playPause':
8427 e.stopPropagation();
8428 SlideshowToggle();
8429 break;
8430 case 'zoomIn':
8431 StopPropagationPreventDefault(e);
8432 if( ViewerZoomStart() ) { ViewerZoomIn( true ); }
8433 break;
8434 case 'zoomOut':
8435 StopPropagationPreventDefault(e);
8436 if( ViewerZoomStart() ) { ViewerZoomIn( false ); }
8437 break;
8438 case 'minimize':
8439 // toggle toolbar visibility
8440 StopPropagationPreventDefault(e);
8441 if( G.VOM.toolbarMode == 'std' ) {
8442 ViewerToolbarForVisibilityMin();
8443 }
8444 else {
8445 ViewerToolbarForVisibilityStd();
8446 }
8447 break;
8448 case 'fullScreen':
8449 // Toggle viewer fullscreen mode on/off
8450 e.stopPropagation();
8451 if( ngscreenfull.enabled ) {
8452 ngscreenfull.toggle();
8453 }
8454 break;
8455 case 'info':
8456 e.stopPropagation();
8457 ItemDisplayInfo(G.VOM.NGY2Item(0));
8458 break;
8459 case 'close':
8460 StopPropagationPreventDefault(e);
8461 if( (new Date().getTime()) - G.VOM.timeImgChanged < 400 ) { return; }
8462 CloseInternalViewer(G.VOM.currItemIdx);
8463 break;
8464 case 'download':
8465 StopPropagationPreventDefault(e);
8466 DownloadImage(G.VOM.items[G.VOM.currItemIdx].ngy2ItemIdx);
8467 break;
8468 case 'share':
8469 StopPropagationPreventDefault(e);
8470 PopupShare(G.VOM.items[G.VOM.currItemIdx].ngy2ItemIdx);
8471 break;
8472 case 'linkOriginal':
8473 StopPropagationPreventDefault(e);
8474 OpenOriginal( G.VOM.NGY2Item(0) );
8475 break;
8476 case 'rotateLeft':
8477 StopPropagationPreventDefault(e);
8478 ViewerImageRotate(-90);
8479 break;
8480 case 'rotateRight':
8481 StopPropagationPreventDefault(e);
8482 ViewerImageRotate(90);
8483 break;
8484 }
8485
8486 // custom button
8487 var fu = G.O.fnImgToolbarCustClick;
8488 if( ngy2action.indexOf('custom') == 0 && fu !== null ) {
8489 typeof fu == 'function' ? fu(ngy2action, $this, G.VOM.NGY2Item(0)) : window[fu](ngy2action, $this, G.VOM.NGY2Item(0));
8490 }
8491 }
8492
8493 // rotate displayed image
8494 function ViewerImageRotate( angle ) {
8495 var item = G.VOM.NGY2Item(0);
8496 if( item.mediaKind == 'img' ) {
8497 item.rotationAngle += angle;
8498 item.rotationAngle = item.rotationAngle % 360;
8499 if( item.rotationAngle < 0 ) {
8500 item.rotationAngle += 360;
8501 }
8502 ViewerMediaPanX( 0 );
8503 ViewerMediaSetPosAndZoomOne( G.VOM.NGY2Item(0), G.VOM.$mediaCurrent, true );
8504 }
8505 }
8506
8507
8508 // Display photo infos
8509 function ItemDisplayInfo( item ) {
8510
8511 var content = '<div class="nGY2PopupOneItemText">' + item.title + '</div>';
8512 content += '<div class="nGY2PopupOneItemText">' + item.description + '</div>';
8513 if( item.author != '' ) {
8514 content += '<div class="nGY2PopupOneItemText">' + G.O.icons.user + ' ' + item.author + '</div>';
8515 }
8516 if( item.exif.model != '' ) {
8517 content += '<div class="nGY2PopupOneItemText">' + G.O.icons.config + ' ' + item.exif.model + '</div>';
8518 }
8519 var sexif = '';
8520 sexif += item.exif.flash == '' ? '' : ' &nbsp; ' + item.exif.flash;
8521 sexif += item.exif.focallength == '' ? '' : ' &nbsp; ' + item.exif.focallength+'mm';
8522 sexif += item.exif.fstop == '' ? '' : ' &nbsp; f' + item.exif.fstop;
8523 sexif += item.exif.exposure == '' ? '' : ' &nbsp; ' + item.exif.exposure+'s';
8524 sexif += item.exif.iso == '' ? '' : ' &nbsp; ' + item.exif.iso+' ISO';
8525 if( item.exif.time != '' ) {
8526 // var date = new Date(parseInt(item.exif.time));
8527 // sexif += ' &nbsp; '+date.toLocaleDateString();
8528 sexif += ' &nbsp; ' + item.exif.time;
8529 }
8530 content += '<div class="nGY2PopupOneItemText">' + sexif + '</div>';
8531
8532 if( item.exif.location != '' ) {
8533 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>';
8534 // embed google map in iframe (no api key required)
8535 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>';
8536 }
8537
8538 Popup(G.O.icons.viewerInfo, content, 'Left');
8539
8540 }
8541
8542
8543
8544 function ToolbarAddElt( elt ) {
8545 var r = '<div class="ngbt ngy2viewerToolAction ',
8546 e=elt.replace(/^\s+|\s+$/g, ''); // remove trailing/leading whitespace
8547 switch( e ) {
8548 case 'minimizeButton':
8549 var ic = G.O.icons.viewerToolbarMin;
8550 if( G.VOM.toolbarMode == 'min' ) {
8551 ic = G.O.icons.viewerToolbarStd;
8552 }
8553 r += 'minimizeButton nGEvent" data-ngy2action="minimize">'+ic+'</div>';
8554 break;
8555 case 'previousButton':
8556 r += 'previousButton nGEvent" data-ngy2action="previous">'+G.O.icons.viewerPrevious+'</div>';
8557 break;
8558 case 'pageCounter':
8559 r += 'pageCounter nGEvent"></div>';
8560 break;
8561 case 'nextButton':
8562 r += 'nextButton nGEvent" data-ngy2action="next">'+G.O.icons.viewerNext+'</div>';
8563 break;
8564 case 'playPauseButton':
8565 r += 'playButton playPauseButton nGEvent" data-ngy2action="playPause">'+G.O.icons.viewerPlay+'</div>';
8566 break;
8567 case 'rotateLeft':
8568 r += 'rotateLeftButton nGEvent" data-ngy2action="rotateLeft">'+G.O.icons.viewerRotateLeft+'</div>';
8569 break;
8570 case 'rotateRight':
8571 r += 'rotateRightButton nGEvent" data-ngy2action="rotateRight">'+G.O.icons.viewerRotateRight+'</div>';
8572 break;
8573 case 'downloadButton':
8574 r += 'downloadButton nGEvent" data-ngy2action="download">'+G.O.icons.viewerDownload+'</div>';
8575 break;
8576 case 'zoomButton':
8577 r += 'nGEvent" data-ngy2action="zoomIn">'+G.O.icons.viewerZoomIn+'</div><div class="ngbt ngy2viewerToolAction nGEvent" data-ngy2action="zoomOut">'+G.O.icons.viewerZoomOut+'</div>';
8578 break;
8579 case 'fullscreenButton':
8580 var s = G.O.icons.viewerFullscreenOn;
8581 if( ngscreenfull.enabled && G.VOM.viewerIsFullscreen ) {
8582 s = G.O.icons.viewerFullscreenOff;
8583 }
8584 r += 'setFullscreenButton fullscreenButton nGEvent" data-ngy2action="fullScreen">'+s+'</div>';
8585 break;
8586 case 'infoButton':
8587 r += 'infoButton nGEvent" data-ngy2action="info">'+G.O.icons.viewerInfo+'</div>';
8588 break;
8589 case 'linkOriginalButton':
8590 r += 'linkOriginalButton nGEvent" data-ngy2action="linkOriginal">' + G.O.icons.viewerLinkOriginal + '</div>';
8591 break;
8592 case 'closeButton':
8593 r += 'closeButton nGEvent" data-ngy2action="close">'+G.O.icons.buttonClose+'</div>';
8594 break;
8595 case 'shareButton':
8596 r += 'nGEvent" data-ngy2action="share">'+G.O.icons.viewerShare+'</div>';
8597 break;
8598 case 'label':
8599 r += '"><div class="title nGEvent" itemprop="name"></div><div class="description nGEvent" itemprop="description"></div></div>';
8600 break;
8601 default:
8602 // custom button
8603 if( e.indexOf('custom') == 0 ) {
8604 var t = '';
8605 // content to display from custom script
8606 var fu = G.O.fnImgToolbarCustInit;
8607 if( fu !== null ) {
8608 typeof fu == 'function' ? fu(e) : window[fu](e);
8609 }
8610 if( t == undefined || t == '' ) {
8611 // content from icons
8612 var n = e.substring(6);
8613 t = G.O.icons['viewerCustomTool'+n];
8614 }
8615 r += 'ngy2CustomBtn ' + e + ' nGEvent" data-ngy2action="' + e + '">' + t + '</div>';
8616 }
8617 else {
8618 r = '';
8619 }
8620 break;
8621 }
8622 return r;
8623 }
8624
8625
8626 // toggle slideshow mode on/off
8627 function SlideshowToggle(){
8628 if( G.VOM.playSlideshow ) {
8629 window.clearTimeout(G.VOM.playSlideshowTimerID);
8630 G.VOM.playSlideshow = false;
8631 G.VOM.$viewer.find('.playPauseButton').html(G.O.icons.viewerPlay);
8632 }
8633 else {
8634 G.VOM.playSlideshow = true;
8635 DisplayNextMedia();
8636 G.VOM.$viewer.find('.playPauseButton').html(G.O.icons.viewerPause);
8637 }
8638 }
8639
8640 function ViewerToolbarForVisibilityStd() {
8641 G.VOM.toolbarMode = 'std';
8642
8643 var sTB = '';
8644 var t = G.O.viewerToolbar.standard.split(',');
8645 for( var i = 0, lt = t.length; i < lt; i++) {
8646 sTB += ToolbarAddElt( t[i] );
8647 }
8648 G.VOM.$toolbar.find('.toolbar').html(sTB);
8649 ViewerToolbarElementContent();
8650 }
8651
8652 function ViewerToolbarForVisibilityMin() {
8653 if( G.O.viewerToolbar.minimized == undefined || G.O.viewerToolbar.minimized == '' ) {
8654 ViewerToolbarForVisibilityStd();
8655 }
8656 else {
8657 G.VOM.toolbarMode = 'min';
8658 var sTB = '';
8659 var t = G.O.viewerToolbar.minimized.split(',');
8660 for( var i = 0, lt = t.length; i < lt; i++) {
8661 sTB += ToolbarAddElt( t[i] );
8662 }
8663 G.VOM.$toolbar.find('.toolbar').html(sTB);
8664 ViewerToolbarElementContent();
8665 }
8666 }
8667
8668 function ViewerToolbarElementContent() {
8669
8670 var vomIdx=G.VOM.currItemIdx;
8671 if( vomIdx == null ) { return; }
8672
8673 var item=G.VOM.NGY2Item(0);
8674
8675 // LABEL
8676 var setTxt = false;
8677 // set title
8678 if( item.title !== undefined && item.title != '' ) {
8679 G.VOM.$viewer.find('.ngy2viewerToolAction').find('.title').html(item.title);
8680 setTxt = true;
8681 }
8682 else {
8683 G.VOM.$viewer.find('.ngy2viewerToolAction').find('.title').html('');
8684 }
8685 // set description
8686 if( item.description !== undefined && item.description != '' ) {
8687 G.VOM.$viewer.find('.ngy2viewerToolAction').find('.description').html(item.description);
8688 setTxt = true;
8689 }
8690 else {
8691 G.VOM.$viewer.find('.ngy2viewerToolAction').find('.description').html('');
8692 }
8693
8694 if( setTxt ) {
8695 G.VOM.$viewer.find('.ngy2viewerToolAction').find('.label').show();
8696 }
8697 else {
8698 G.VOM.$viewer.find('.ngy2viewerToolAction').find('.label').hide();
8699 }
8700
8701 // set page number
8702 var viewerMaxImages = G.VOM.items.length;
8703 if( viewerMaxImages > 0 ) {
8704 G.VOM.$viewer.find('.pageCounter').html((G.VOM.items[vomIdx].mediaNumber)+'/'+viewerMaxImages);
8705 }
8706
8707 // custom elements
8708 var $cu = G.VOM.$viewer.find('.ngy2CustomBtn');
8709 var fu = G.O.fnImgToolbarCustDisplay;
8710 if( $cu.length > 0 && fu !== null ) {
8711 typeof fu == 'function' ? fu($cu, item) : window[fu]($cu, item);
8712 }
8713
8714 // set event handlers again
8715 ViewerToolsOn();
8716 }
8717
8718 // Pan the media container in the lightbox (left/right)
8719 function ViewerMediaPanX( posX ) {
8720 G.VOM.swipePosX = posX;
8721 if( G.CSStransformName == null ) {
8722 // no pan if CSS transform not supported
8723 // G.VOM.$mediaCurrent.css({ left: posX });
8724 }
8725 else {
8726
8727 // pan left/right the current media
8728 // window.ng_draf( function() {
8729 G.VOM.$mediaCurrent[0].style[G.CSStransformName] = 'translate(' + posX + 'px, 0px)';
8730 // });
8731
8732 var itemPrevious = G.VOM.NGY2Item(-1);
8733 var itemNext = G.VOM.NGY2Item(1);
8734
8735 // next/previous media
8736 if( G.O.imageTransition.startsWith('SWIPE') ) {
8737 if( itemPrevious.mediaTransition() ) {
8738 ViewerSetMediaVisibility(G.VOM.NGY2Item(-1), G.VOM.$mediaPrevious, 1);
8739 }
8740 if( itemNext.mediaTransition() ) {
8741 ViewerSetMediaVisibility(G.VOM.NGY2Item(1), G.VOM.$mediaNext, 1);
8742 }
8743
8744 var sc = Math.min( Math.max( Math.abs(posX) / G.VOM.window.lastWidth, .8), 1);
8745 if( G.O.imageTransition == 'SWIPE' ) { sc = 1; }
8746
8747 if( posX > 0 ) {
8748 var dir = G.VOM.window.lastWidth;
8749 if( itemPrevious.mediaTransition() ) {
8750 // window.ng_draf( function() {
8751 G.VOM.$mediaPrevious[0].style[G.CSStransformName] = 'translate(' + (-dir + posX) + 'px, 0px) scale(' + sc + ')';
8752 // });
8753 }
8754 if( itemNext.mediaTransition() ) {
8755 // window.ng_draf( function() {
8756 G.VOM.$mediaNext[0].style[G.CSStransformName] = 'translate(' + (dir) + 'px, 0px) scale(' + sc + ')';
8757 // });
8758 }
8759 }
8760 else {
8761 var dir = -G.VOM.window.lastWidth;
8762 if( itemNext.mediaTransition() ) {
8763 // window.ng_draf( function() {
8764 G.VOM.$mediaNext[0].style[G.CSStransformName] = 'translate(' + (-dir + posX) + 'px, 0px) scale(' + sc + ')';
8765 // });
8766 }
8767 if( itemPrevious.mediaTransition() ) {
8768 // window.ng_draf( function() {
8769 G.VOM.$mediaPrevious[0].style[G.CSStransformName] = 'translate(' + (dir) + 'px, 0px) scale(' + sc + ')';
8770 // });
8771 }
8772 }
8773 }
8774
8775
8776 if( G.O.imageTransition == 'SLIDEAPPEAR' ) {
8777 G.VOM.$mediaPrevious[0].style[G.CSStransformName] = '';
8778 G.VOM.$mediaNext[0].style[G.CSStransformName] = '';
8779 if( posX < 0 ) {
8780 var o = (-posX) / G.VOM.window.lastWidth;
8781 if( itemNext.mediaTransition() ) {
8782 ViewerSetMediaVisibility(itemNext, G.VOM.$mediaNext, o);
8783 }
8784 if( itemPrevious.mediaTransition() ) {
8785 ViewerSetMediaVisibility(itemPrevious, G.VOM.$mediaPrevious, 0);
8786 }
8787 }
8788 else {
8789 var o = posX / G.VOM.window.lastWidth;
8790 if( itemPrevious.mediaTransition() ) {
8791 ViewerSetMediaVisibility(itemPrevious, G.VOM.$mediaPrevious, o);
8792 }
8793 if( itemNext.mediaTransition() ) {
8794 ViewerSetMediaVisibility(itemNext, G.VOM.$mediaNext, 0);
8795 }
8796 }
8797 }
8798 }
8799 }
8800
8801 // Display next image
8802 function DisplayNextMedia() {
8803 if( G.VOM.viewerMediaIsChanged || ((new Date().getTime()) - G.VOM.timeImgChanged < 300) ) { return; }
8804
8805 TriggerCustomEvent('lightboxNextImage');
8806 DisplayInternalViewer(G.VOM.IdxNext(), 'nextImage');
8807 };
8808
8809 // Display previous image
8810 function DisplayPreviousMedia() {
8811 if( G.VOM.viewerMediaIsChanged || ((new Date().getTime()) - G.VOM.timeImgChanged < 300) ) { return; }
8812 if( G.VOM.playSlideshow ) {
8813 SlideshowToggle();
8814 }
8815
8816 TriggerCustomEvent('lightboxPreviousImage');
8817 DisplayInternalViewer(G.VOM.IdxPrevious(), 'previousImage');
8818 };
8819
8820 // Display image (and run animation)
8821 function DisplayInternalViewer( newVomIdx, displayType ) {
8822
8823 if( G.VOM.playSlideshow ) { window.clearTimeout(G.VOM.playSlideshowTimerID); }
8824
8825 var itemOld = G.VOM.NGY2Item(0);
8826 var itemNew = G.I[G.VOM.items[newVomIdx].ngy2ItemIdx];
8827 var $new = (displayType == 'nextImage' ? G.VOM.$mediaNext : G.VOM.$mediaPrevious);
8828 if( displayType == 'nextImage' ) {
8829 ViewerSetMediaVisibility(G.VOM.NGY2Item(-1), G.VOM.$mediaPrevious, 0);
8830 }
8831 else {
8832 ViewerSetMediaVisibility(G.VOM.NGY2Item(1), G.VOM.$mediaNext, 0);
8833 }
8834
8835 G.VOM.timeImgChanged = new Date().getTime();
8836 G.VOM.viewerMediaIsChanged = true;
8837 G.VOM.zoom.isZooming = false;
8838 ResizeInternalViewer(true);
8839
8840 if( G.O.debugMode && console.timeline ) { console.timeline('nanogallery2_viewer'); }
8841
8842 SetLocationHash( itemNew.albumID, itemNew.GetID() );
8843
8844 // animation duration is proportional of the remaining distance
8845 var vP = G.GOM.cache.viewport;
8846 var dur = 400 * (vP.w - Math.abs(G.VOM.swipePosX)) / vP.w;
8847
8848 if( displayType == '' ) {
8849 // first image --> just appear / no slide animation
8850 ViewerSetMediaVisibility(G.VOM.NGY2Item(0), G.VOM.$mediaCurrent, 1);
8851 if( G.CSStransformName == null ) {
8852 // no CSS transform support -> no animation
8853 ViewerSetMediaVisibility(itemNew, $new, 1);
8854 DisplayInternalViewerComplete(displayType, newVomIdx);
8855 }
8856 else {
8857 ViewerSetMediaVisibility(itemNew, $new, 0);
8858 new NGTweenable().tween({
8859 from: { opacity: 0 },
8860 to: { opacity: 1 },
8861 attachment: { dT: displayType, item: itemOld },
8862 easing: 'easeInOutSine',
8863 delay: 30,
8864 duration: 400,
8865 step: function (state, att) {
8866 // using scale is not a good idea on Chrome -> image will be blurred
8867 G.VOM.$content.css('opacity', state.opacity);
8868 },
8869 finish: function (state, att) {
8870 ViewerToolsUnHide();
8871 DisplayInternalViewerComplete(att.dT, newVomIdx);
8872 }
8873 });
8874 }
8875 }
8876 else {
8877 // animate the image transition between 2 images
8878 if( G.CSStransformName == null ) {
8879 // no CSS transform support -> no animation
8880 ViewerSetMediaVisibility(itemNew, $new, 1);
8881 ViewerSetMediaVisibility(G.VOM.NGY2Item(0), G.VOM.$mediaCurrent, 1);
8882 DisplayInternalViewerComplete(displayType, newVomIdx);
8883 }
8884 else {
8885 switch( G.O.imageTransition ) {
8886 case 'SWIPE':
8887 case 'SWIPE2':
8888 var dir = ( displayType == 'nextImage' ? - vP.w : vP.w );
8889 $new[0].style[G.CSStransformName] = 'translate('+(-dir)+'px, 0px) '
8890
8891 new NGTweenable().tween({
8892 from: { t: G.VOM.swipePosX },
8893 to: { t: (displayType == 'nextImage' ? - vP.w : vP.w) },
8894 attachment: { dT: displayType, $e: $new, itemNew: itemNew, itemOld: itemOld, dir: dir },
8895 delay: 30,
8896 duration: dur,
8897 easing: (G.O.imageTransition == 'swipe' ? 'easeInOutSine' : 'easeInOutCubic'),
8898 step: function (state, att) {
8899 // current media
8900 ViewerSetMediaVisibility(G.VOM.NGY2Item(0), G.VOM.$mediaCurrent, 1);
8901 // G.VOM.$mediaCurrent[0].style[G.CSStransformName] = 'translate(' + state.t + 'px, 0px) rotate(' + att.itemOld.rotationAngle + 'deg)';
8902 G.VOM.$mediaCurrent[0].style[G.CSStransformName] = 'translate(' + state.t + 'px, 0px)';
8903 // new media
8904 if( att.itemNew.mediaTransition() ) {
8905 ViewerSetMediaVisibility(att.itemNew, att.$e, 1);
8906
8907 var sc = Math.min( Math.max( (Math.abs(state.t)) / G.VOM.window.lastWidth, .8), 1);
8908 if( G.O.imageTransition == 'SWIPE' ) { sc = 1; }
8909 // att.$e[0].style[G.CSStransformName] = 'translate(' + (-att.dir+state.t) + 'px, 0px) scale(' + sc + ') rotate(' + att.itemNew.rotationAngle + 'deg)';
8910 att.$e[0].style[G.CSStransformName] = 'translate(' + (-att.dir+state.t) + 'px, 0px) scale(' + sc + ')';
8911 }
8912 },
8913 finish: function (state, att) {
8914 G.VOM.$mediaCurrent[0].style[G.CSStransformName] = '';
8915 att.$e[0].style[G.CSStransformName] = '';
8916 DisplayInternalViewerComplete(att.dT, newVomIdx);
8917 }
8918 });
8919 break;
8920
8921 case 'SLIDEAPPEAR':
8922 default:
8923 var dir=(displayType == 'nextImage' ? - vP.w : vP.w);
8924 var op = (Math.abs(G.VOM.swipePosX)) / G.VOM.window.lastWidth;
8925 $new[0].style[G.CSStransformName] = '';
8926 new NGTweenable().tween({
8927 from: { o: op, t: G.VOM.swipePosX },
8928 to: { o: 1, t: (displayType == 'nextImage' ? - vP.w : vP.w) },
8929 attachment: { dT:displayType, $e:$new, itemNew: itemNew, itemOld: itemOld, dir: dir },
8930 delay: 30,
8931 duration: dur,
8932 easing: 'easeInOutSine',
8933 step: function (state, att) {
8934 // current media - translate
8935 // G.VOM.$mediaCurrent[0].style[G.CSStransformName]= 'translate('+state.t+'px, 0px) rotate(' + att.itemOld.rotationAngle + 'deg)';
8936 G.VOM.$mediaCurrent[0].style[G.CSStransformName]= 'translate('+state.t+'px, 0px)';
8937 // new media - opacity
8938 if( att.itemNew.mediaTransition() ) {
8939 ViewerSetMediaVisibility(att.itemNew, att.$e, state.o);
8940 }
8941 },
8942 finish: function (state, att) {
8943 G.VOM.$mediaCurrent[0].style[G.CSStransformName]= '';
8944 DisplayInternalViewerComplete(att.dT, newVomIdx);
8945 }
8946 });
8947 break;
8948 }
8949 }
8950 }
8951 }
8952
8953
8954 function DisplayInternalViewerComplete( displayType, newVomIdx ) {
8955 G.VOM.currItemIdx = newVomIdx;
8956
8957 var ngy2item = G.VOM.NGY2Item(0);
8958
8959 ViewerToolbarElementContent();
8960 if( G.O.debugMode && console.timeline ) { console.timelineEnd('nanogallery2_viewer'); }
8961
8962 var fu=G.O.fnImgDisplayed;
8963 if( fu !== null ) {
8964 typeof fu == 'function' ? fu(ngy2item) : window[fu](ngy2item);
8965 }
8966
8967 G.VOM.swipePosX = 0;
8968 if( displayType != '' ) {
8969 // not on first media display
8970 // G.VOM.$mediaCurrent.off("click");
8971 G.VOM.$mediaCurrent.removeClass('imgCurrent');
8972
8973 var $tmp = G.VOM.$mediaCurrent;
8974 switch( displayType ) {
8975 case 'nextImage':
8976 G.VOM.$mediaCurrent = G.VOM.$mediaNext;
8977 G.VOM.$mediaNext = $tmp;
8978 break;
8979 case 'previousImage':
8980 G.VOM.$mediaCurrent = G.VOM.$mediaPrevious;
8981 G.VOM.$mediaPrevious = $tmp;
8982 break;
8983 }
8984 }
8985
8986 G.VOM.$mediaCurrent.addClass('imgCurrent');
8987
8988 // re-sort the media containers --> current on top
8989 var $pans = G.VOM.$content.find('.nGY2ViewerMediaPan');
8990 G.VOM.$mediaCurrent.insertAfter($pans.last());
8991
8992 if( ngy2item.mediaKind == 'img' && ngy2item.imageWidth == 0 ) {
8993 ViewerSetMediaVisibility(G.VOM.NGY2Item(0), G.VOM.$mediaCurrent, 0);
8994 }
8995 else {
8996 G.VOM.$mediaCurrent.children().eq(0).attr('class', 'nGY2ViewerMediaLoaderHidden'); // hide preloader
8997 ViewerSetMediaVisibility(G.VOM.NGY2Item(0), G.VOM.$mediaCurrent, 1);
8998 }
8999
9000
9001 // set the new NEXT media
9002 G.VOM.$mediaNext.empty();
9003 var nextItem = G.VOM.NGY2Item(1);
9004 var spreloader = '<div class="nGY2ViewerMediaLoaderDisplayed"></div>';
9005 if( nextItem.mediaKind == 'img' && nextItem.imageWidth != 0 && nextItem.imageHeight != 0 ) {
9006 spreloader = '<div class="nGY2ViewerMediaLoaderHidden"></div>';
9007 }
9008 G.VOM.$mediaNext.append( spreloader + nextItem.mediaMarkup );
9009 ViewerSetMediaVisibility(nextItem, G.VOM.$mediaNext, 0);
9010 if( nextItem.mediaKind == 'img' ) {
9011 G.VOM.ImageLoader.loadImage(VieweImgSizeRetrieved, nextItem);
9012 }
9013 else {
9014 ViewerMediaCenterNotImg( G.VOM.$mediaNext );
9015 }
9016
9017 // set the new PREVIOUS media
9018 G.VOM.$mediaPrevious.empty();
9019 var previousItem = G.VOM.NGY2Item(-1);
9020 spreloader = '<div class="nGY2ViewerMediaLoaderDisplayed"></div>';
9021 if( previousItem.mediaKind == 'img' && previousItem.imageWidth != 0 && previousItem.imageHeight != 0 ) {
9022 spreloader = '<div class="nGY2ViewerMediaLoaderHidden"></div>';
9023 }
9024 G.VOM.$mediaPrevious.append( spreloader + previousItem.mediaMarkup );
9025 ViewerSetMediaVisibility(previousItem, G.VOM.$mediaPrevious, 0);
9026 if( previousItem.mediaKind == 'img' ) {
9027 G.VOM.ImageLoader.loadImage( VieweImgSizeRetrieved, previousItem );
9028 }
9029 else {
9030 ViewerMediaCenterNotImg( G.VOM.$mediaPrevious );
9031 }
9032
9033
9034 // slideshow mode - wait until image is loaded to start the delay for next image
9035 if( G.VOM.playSlideshow ) {
9036 G.VOM.$mediaCurrent.children().eq(1).ngimagesLoaded().always( function( instance ) {
9037 if( G.VOM.playSlideshow ) {
9038 // in the meantime the user could have stopped the slideshow
9039 G.VOM.playSlideshowTimerID = window.setTimeout( function(){ DisplayNextMedia(); }, G.VOM.slideshowDelay );
9040 }
9041 });
9042 }
9043
9044 // close viewer when user clicks outside of the image
9045 // G.VOM.$mediaCurrent.on("click",function(e){
9046 // e.stopPropagation();
9047 // if( (new Date().getTime()) - G.VOM.timeImgChanged < 400 ) { return; }
9048 // StopPropagationPreventDefault(e);
9049 // CloseInternalViewer(G.VOM.currItemIdx);
9050 // return false;
9051 // });
9052
9053 ResizeInternalViewer();
9054
9055 G.VOM.viewerMediaIsChanged = false;
9056 TriggerCustomEvent('lightboxImageDisplayed');
9057
9058 }
9059
9060
9061 // Is fired as soon as the size of an image has been retrieved (the download may not be finished)
9062 function VieweImgSizeRetrieved(w, h, item, n) {
9063 item.imageWidth = w;
9064 item.imageHeight = h;
9065
9066 // image sized retrieved for currently displayed media
9067 // if( G.VOM.$mediaCurrent !== null && G.VOM.$mediaCurrent.children().attr('src') == item.responsiveURL() ) {
9068 if( G.VOM.NGY2Item(0) == item ) {
9069 // it is the current displayed media
9070 G.VOM.$mediaCurrent.children().eq(0).attr('class', 'nGY2ViewerMediaLoaderHidden'); // hide preloader
9071 ViewerSetMediaVisibility(G.VOM.NGY2Item(0), G.VOM.$mediaCurrent, 1);
9072 G.VOM.zoom.userFactor = 1;
9073 }
9074
9075 if( G.VOM.NGY2Item(1) == item ) { // next media
9076 G.VOM.$mediaNext.children().eq(0).attr('class', 'nGY2ViewerMediaLoaderHidden'); // hide preloader
9077 }
9078 if( G.VOM.NGY2Item(-1) == item ) { // previous media
9079 G.VOM.$mediaPrevious.children().eq(0).attr('class', 'nGY2ViewerMediaLoaderHidden'); // hide preloader
9080 }
9081
9082 ViewerMediaSetPosAndZoom();
9083
9084 }
9085
9086 // Viewer - Set the visibility of the media and it's container
9087 function ViewerSetMediaVisibility(item, $media, opacity ) {
9088
9089 if( item.mediaKind == 'img' && item.imageWidth == 0 ) {
9090 // do not display image if width is unknown (--> callback will set the width when know)
9091 // setting opacity to 0 is not enough -> it is mandatory to change also the visibility to hidden to avoid responds to events (click/touch)
9092 // $media.children().css({ opacity: 0, visibility: 'hidden' });
9093 $media.children().eq(1).css({ opacity: 0, visibility: 'hidden' }); // hide media
9094 // $media.css({ opacity: 0, visibility: 'hidden' });
9095 return;
9096 }
9097
9098 if( opacity == 0 ) {
9099 // setting opacity to 0 is not enough -> it is mandatory to change also the visibility to hidden to avoid responds to events (click/touch)
9100 // $media.css({ opacity: 0, visibility: 'hidden' });
9101 $media.children().css({ opacity: 0, visibility: 'hidden' }); // hide media
9102 }
9103 else {
9104 // $media.css({ opacity: opacity, visibility: 'visible' });
9105 $media.children().css({ opacity: opacity, visibility: 'visible' }); // display media
9106 }
9107 }
9108
9109
9110 // Close the internal lightbox
9111 function CloseInternalViewer( vomIdx ) {
9112
9113 G.VOM.viewerMediaIsChanged = false;
9114
9115 if( G.VOM.viewerDisplayed ) {
9116
9117 // set scrollbar visible again
9118 jQuery('body').css({ overflowX: G.VOM.saveOverflowX, overflowY: G.VOM.saveOverflowY});
9119 // jQuery('body').css({overflow: 'visible'});
9120
9121
9122 if( G.VOM.playSlideshow ) {
9123 window.clearTimeout(G.VOM.playSlideshowTimerID);
9124 G.VOM.playSlideshow = false;
9125 }
9126
9127 // G.VOM.userEvents.removeEventListeners();
9128 // G.VOM.userEvents=null;
9129 G.VOM.hammertime.destroy();
9130 G.VOM.hammertime = null;
9131
9132 if( ngscreenfull.enabled && G.VOM.viewerIsFullscreen ) {
9133 G.VOM.viewerIsFullscreen = false;
9134 ngscreenfull.exit();
9135 }
9136
9137 G.VOM.$cont.hide(0).off().show(0).html('').remove();
9138 G.VOM.viewerDisplayed = false;
9139
9140 if( G.O.thumbnailAlbumDisplayImage ) {
9141 // content of album displayed directly in lightbox (no gallery display for album content)
9142 if( vomIdx == null ) {
9143 // lightbox closed with browser back-button
9144 // the gallery is already displayed
9145 }
9146 else {
9147 var item = G.I[G.VOM.items[vomIdx].ngy2ItemIdx];
9148 var parent = NGY2Item.Get(G, item.albumID);
9149 if( G.GOM.albumIdx != parent.albumID ) {
9150 // display album only if not already displayed
9151 DisplayAlbum('-1', parent.albumID);
9152 }
9153 else {
9154 GalleryResize();
9155 SetLocationHash( '', '' );
9156 ThumbnailHoverReInitAll();
9157 }
9158 }
9159 // DisplayAlbum( '-', G.I[G.VOM.items[vomIdx].ngy2ItemIdx].albumID );
9160 }
9161 else {
9162 if( vomIdx != null ) {
9163 if( G.GOM.albumIdx == -1 ) {
9164 // album not displayed --> display gallery
9165 DisplayAlbum( '', G.I[G.VOM.items[vomIdx].ngy2ItemIdx].albumID );
9166 }
9167 else {
9168 GalleryResize();
9169 SetLocationHash( G.I[G.VOM.items[vomIdx].ngy2ItemIdx].albumID, '' );
9170 ThumbnailHoverReInitAll();
9171 }
9172 }
9173 }
9174 G.VOM.timeImgChanged = new Date().getTime();
9175 }
9176 }
9177
9178
9179 // Internal viewer resized -> reposition elements
9180 function ResizeInternalViewer( forceUpdate ) {
9181 forceUpdate = typeof forceUpdate !== 'undefined' ? forceUpdate : false;
9182
9183 if( G.VOM.$toolbar === null ) { return; } // viewer build not finished
9184
9185
9186 // window.requestAnimationFrame( function() { // synchronize with screen
9187 var windowsW = G.VOM.$viewer.width();
9188 var windowsH = G.VOM.$viewer.height();
9189 var $elt = G.VOM.$mediaCurrent.children().eq(1);
9190 if( $elt == null || G.VOM.currItemIdx == -1 ) { return; }
9191
9192 if( !forceUpdate && G.VOM.window.lastWidth == windowsW && G.VOM.window.lastHeight == windowsH ) { return; }
9193
9194 G.VOM.window.lastWidth = windowsW;
9195 G.VOM.window.lastHeight = windowsH;
9196
9197 // var vwImgC_H=$elt.height(),
9198 // vwImgC_W=$elt.width(),
9199 // vwImgC_OHt=$elt.outerHeight(true),
9200 // vwImgC_OHf=$elt.outerHeight(false);
9201
9202 var $tb = G.VOM.$toolbar.find('.toolbar');
9203 var tb_OHt = $tb.outerHeight(true);
9204
9205 switch( G.O.viewerToolbar.position ) {
9206 case 'topOverImage':
9207 G.VOM.$content.css({height: windowsH, width: windowsW, top: 0 });
9208 G.VOM.$toolbar.css({top: 0, bottom: ''});
9209 break;
9210 case 'top':
9211 windowsH -= tb_OHt;
9212 G.VOM.$content.css({height: windowsH, width: windowsW, top: tb_OHt });
9213 G.VOM.$toolbar.css({top: 0});
9214 break;
9215 case 'bottomOverImage':
9216 G.VOM.$content.css({height:windowsH, width: windowsW, bottom: 0, top: 0 });
9217 G.VOM.$toolbar.css({bottom: 0});
9218 break;
9219 case 'bottom':
9220 default:
9221 windowsH -= tb_OHt;
9222 G.VOM.$content.css({ width: windowsW, top: 0, bottom: tb_OHt });
9223 G.VOM.$toolbar.css({bottom: 0});
9224 break;
9225 }
9226
9227 if( !G.VOM.viewerMediaIsChanged && G.VOM.zoom.isZooming ) {
9228 ViewerMediaSetPosAndZoom();
9229 }
9230 else {
9231 if( !G.VOM.zoom.isZooming && ( G.VOM.zoom.userFactor != 0 || G.VOM.panPosX != 0 || G.VOM.panPosY != 0 || G.VOM.zoom.posX != 0 || G.VOM.zoom.posY != 0 )) {
9232 // animate image zoom factor and position back to initial values
9233 G.VOM.zoom.isZooming= true; // activate zooming temporarily
9234 new NGTweenable().tween({
9235 from: { userFactor: G.VOM.zoom.userFactor, panPosX: G.VOM.panPosX, panPosY: G.VOM.panPosY, zoomPosX: G.VOM.zoom.posX, zoomPosY: G.VOM.zoom.posY },
9236 to: { userFactor: 1, panPosX: 0, panPosY: 0, zoomPosX: 0, zoomPosY: 0 },
9237 easing: 'easeInOutSine',
9238 delay: 0,
9239 duration: 150,
9240 step: function (state) {
9241 G.VOM.zoom.userFactor=state.userFactor;
9242 G.VOM.panPosX=state.panPosX;
9243 G.VOM.panPosY=state.panPosY;
9244 G.VOM.zoom.posX=state.zoomPosX;
9245 G.VOM.zoom.posY=state.zoomPosY;
9246 ViewerMediaSetPosAndZoom();
9247 },
9248 finish: function (state) {
9249 G.VOM.zoom.isZooming=false;
9250 }
9251 });
9252
9253 }
9254 else {
9255 G.VOM.zoom.userFactor = 1;
9256 G.VOM.zoom.isZooming = false;
9257 G.VOM.panPosX = 0;
9258 G.VOM.panPosY = 0;
9259 G.VOM.zoom.posX = 0;
9260 G.VOM.zoom.posY = 0;
9261 ViewerMediaSetPosAndZoom();
9262 }
9263 }
9264 }
9265
9266
9267
9268 /** @function BuildSkeleton */
9269 /** Build the gallery structure **/
9270 function BuildSkeleton() {
9271
9272 // store markup if defined
9273 var $elements = G.$E.base.children('a');
9274 if( $elements.length > 0 ) {
9275 G.O.$markup = $elements;
9276 }
9277 G.$E.base.text('');
9278 G.$E.base.addClass('ngy2_container');
9279
9280 // RTL or LTR
9281 var sRTL='';
9282 if( G.O.RTL ) {
9283 sRTL = 'style="text-align:right;direction:rtl;"';
9284 }
9285
9286 // theme
9287 G.$E.base.addClass(G.O.theme)
9288 // gallery color scheme
9289 SetGalleryTheme();
9290
9291 // Hide icons (thumbnails and breadcrumb)
9292 if( G.O.thumbnailLabel.get('hideIcons') ) {
9293 G.O.icons.thumbnailAlbum = '';
9294 G.O.icons.thumbnailImage = '';
9295 }
9296
9297 // Navigation bar
9298 var styleNavigation="";
9299 if( G.O.navigationFontSize != undefined && G.O.navigationFontSize != '' ) {
9300 styleNavigation=' style="font-size:'+G.O.navigationFontSize+';"';
9301 }
9302 G.$E.conNavigationBar = jQuery('<div class="nGY2Navigationbar" '+styleNavigation+'></div>').appendTo(G.$E.base);
9303
9304 // pre-loader
9305 G.$E.conLoadingB = jQuery('<div class="nanoGalleryLBarOff"><div></div><div></div><div></div><div></div><div></div></div>').appendTo(G.$E.base);
9306
9307 // gallery
9308 G.$E.conTnParent = jQuery('<div class="nGY2Gallery"></div>').appendTo( G.$E.base );
9309 G.$E.conTn = jQuery('<div class="nGY2GallerySub"></div>').appendTo( G.$E.conTnParent );
9310
9311 // configure gallery
9312 switch( G.O.thumbnailAlignment ) {
9313 case 'left':
9314 G.$E.conTnParent.css({'text-align':'left'});
9315 // G.$E.conNavBCon.css({'margin-left':0 });
9316 break;
9317 case 'right':
9318 G.$E.conTnParent.css({'text-align':'right'});
9319 // G.$E.conNavBCon.css({ 'margin-right':0});
9320 break;
9321 }
9322
9323 // apply galleryBuildInit2 css settings to the gallery
9324 if( G.O.galleryBuildInit2 !== undefined ) {
9325 var t1=G.O.galleryBuildInit2.split('|');
9326 for( var i=0; i<t1.length; i++ ) {
9327 var o1=t1[i].split('_');
9328 if( o1.length == 2 ) {
9329 G.$E.conTn.css(o1[0], o1[1]);
9330 }
9331 }
9332 }
9333
9334 // configure gallery depending on some thumbnail hover effects
9335 var effects=G.tn.hoverEffects.std.concat(G.tn.hoverEffects.level1);
9336 for( var j=0; j<effects.length; j++) {
9337 switch( effects[j].type ) {
9338 case 'scale':
9339 case 'rotateZ':
9340 case 'rotateX':
9341 case 'rotateY':
9342 case 'translateX':
9343 case 'translateY':
9344 // handle some special cases
9345 if( effects[j].element == '.nGY2GThumbnail' ) {
9346 // allow thumbnail upscale over the gallery's aera
9347 G.$E.base.css('overflow', 'visible');
9348 G.$E.base.find('.nGY2GallerySub').css('overflow', 'visible');
9349 G.$E.conTnParent.css('overflow', 'visible');
9350 }
9351 break;
9352 }
9353 }
9354
9355 // Gallery bottom container
9356 G.$E.conTnBottom = jQuery('<div class="nGY2GalleryBottom" '+styleNavigation+'></div>').appendTo( G.$E.conTnParent );
9357
9358 // portable edition
9359 if( G.O.portable ) {
9360 // http://www.picresize.com/
9361 // http://base64encode.net/base64-image-encoder
9362 // 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';
9363 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';
9364 var st = "font-weight:bold !important;color: #FF0075 !important;font-size: 14px !important;text-transform: lowercase !important;cursor:pointer !important;text-align: center !important;Text-Shadow: #000000 1px 0px 0px, #000000 1px 1px 0px, #000000 1px -1px 0px, #000000 -1px 1px 0px, #000000 -1px 0px 0px, #000000 -1px -1px 0px, #000000 0px 1px 0px, #000000 0px -1px 0px !important;";
9365 G.$E.ngy2i=jQuery('<div class="nGY2PortInfo"><a href="http://nano.gallery" target="_blank" title="nanogallery2 | easy photo gallery for your website" style="' + st + '"><img src="' + logo + '" style="height:32px !important;width:initial !important;box-shadow: none !important;vertical-align: middle !important;"/> &nbsp; nanogallery2</a></div>').appendTo(G.$E.base);
9366
9367 G.$E.ngy2i.find('a').on({
9368 mouseenter: function () {
9369 jQuery(this).attr('style', st);
9370 },
9371 mouseleave: function () {
9372 jQuery(this).attr('style', st);
9373 }
9374 });
9375 }
9376
9377 // Error console
9378 G.$E.conConsole = jQuery('<div class="nGY2ConsoleParent"></div>').appendTo(G.$E.base);
9379
9380 // i18n translations
9381 i18n();
9382
9383 // cache some thumbnails data (sizes, styles...)
9384 ThumbnailDefCaches();
9385
9386 // do special settings depending for some options
9387 // thumbnail display transition
9388 switch( G.tn.opt.Get('displayTransition') ) {
9389 case 'SCALEDOWN':
9390 case 'RANDOMSCALE':
9391 default:
9392 G.$E.base.css('overflow', 'visible');
9393 G.$E.conTnParent.css('overflow', 'visible');
9394 G.$E.conTn.css('overflow', 'visible');
9395 break;
9396 }
9397
9398 }
9399
9400 function TriggerCustomEvent ( eventName ) {
9401 // G.$E.base.trigger('pageChanged.nanogallery2', new Event('pageChanged.nanogallery2'));
9402 var eN = eventName + '.nanogallery2';
9403 var event=null;
9404 try {
9405 event = new Event( eN );
9406 } catch(e) {
9407 event = document.createEvent('Event');
9408 event.initEvent(eN, false, false);
9409 }
9410 G.$E.base.trigger(eN, event);
9411 }
9412
9413
9414 /** @function SetGlobalEvents */
9415 function SetGlobalEvents() {
9416 // GLOBAL EVENT MANAGEMENT
9417
9418 G.$E.conTnParent.on({
9419 mouseenter: GalleryMouseEnter,
9420 mouseleave: GalleryMouseLeave
9421 }, ".nGY2GThumbnail"); //pass the element as an argument to .on
9422
9423 // G.GOM.hammertime = new NGHammer(G.$E.conTn[0], { touchAction: 'none' });
9424 G.GOM.hammertime = new NGHammer( G.$E.conTn[0] );
9425 // G.GOM.hammertime.domEvents = true;
9426
9427 G.GOM.hammertime.on('pan', function(ev) {
9428 if( !G.VOM.viewerDisplayed ) {
9429 if( G.O.paginationSwipe && G.layout.support.rows && G.galleryDisplayMode.Get() == 'PAGINATION' ) {
9430 if( Math.abs(ev.deltaY) > G.GOM.panThreshold ) {
9431 G.GOM.panYOnly = true;
9432 }
9433 if( !G.GOM.panYOnly ) {
9434 G.$E.conTn.css( G.CSStransformName , 'translate('+(ev.deltaX)+'px,0px)');
9435 }
9436 }
9437 }
9438 });
9439 G.GOM.hammertime.on('panend', function(ev) {
9440 if( !G.VOM.viewerDisplayed ) {
9441 if( G.O.paginationSwipe && G.layout.support.rows && G.galleryDisplayMode.Get() == 'PAGINATION' ) {
9442 if( !G.GOM.panYOnly ) {
9443 if( ev.deltaX > 50 ) {
9444 paginationPreviousPage();
9445 return;
9446 }
9447 if( ev.deltaX < -50 ) {
9448 paginationNextPage();
9449 return;
9450 }
9451 }
9452 G.GOM.panYOnly = false;
9453 G.$E.conTn.css( G.CSStransformName , 'translate(0px,0px)');
9454 // pX=0;
9455 }
9456 }
9457 });
9458 G.GOM.hammertime.on('tap', function(ev) {
9459 if( !G.VOM.viewerDisplayed ) {
9460 ev.srcEvent.stopPropagation();
9461 ev.srcEvent.preventDefault(); // cancel mouseenter event
9462
9463 if( ev.pointerType == 'mouse') {
9464 if( GalleryClicked(ev.srcEvent) == 'exit' ) { return; }
9465 }
9466 else {
9467 var r = GalleryEventRetrieveElementl(ev.srcEvent, false);
9468 if( r.GOMidx == -1 ) { return; }
9469 if( r.action != 'NONE' && r.action != 'OPEN' ) {
9470 // toolbar touched --> execute action
9471 GalleryClicked(ev.srcEvent);
9472 return;
9473 }
9474
9475 if( G.GOM.slider.hostIdx == r.GOMidx ) {
9476 // touch on thumbnail slider -> open immediately
9477 ThumbnailHoverOutAll();
9478 ThumbnailOpen(G.GOM.items[G.GOM.slider.currentIdx].thumbnailIdx, true);
9479 return;
9480 }
9481
9482 if( (G.GOM.curNavLevel == 'l1' && G.O.touchAnimationL1 == false) || (G.GOM.curNavLevel == 'lN' && G.O.touchAnimation == false) ) {
9483 // open on single touch (no hover animation)
9484 ThumbnailOpen(G.GOM.items[r.GOMidx].thumbnailIdx, true);
9485 return;
9486 }
9487
9488 if( G.O.touchAutoOpenDelay > 0 ) {
9489 // open on single touch after end of hover animation (=defined delay)
9490 ThumbnailHoverOutAll();
9491 ThumbnailHover( r.GOMidx );
9492 window.clearInterval( G.touchAutoOpenDelayTimerID );
9493 G.touchAutoOpenDelayTimerID = window.setInterval(function(){
9494 window.clearInterval( G.touchAutoOpenDelayTimerID );
9495 ThumbnailOpen( G.GOM.items[r.GOMidx].thumbnailIdx, true );
9496 }, G.O.touchAutoOpenDelay );
9497 }
9498 else {
9499 // two touch scenario
9500 if( !G.I[G.GOM.items[r.GOMidx].thumbnailIdx].hovered ) {
9501 ThumbnailHoverOutAll();
9502 ThumbnailHover(r.GOMidx);
9503 }
9504 else {
9505 // second touch
9506 ThumbnailOpen(G.GOM.items[r.GOMidx].thumbnailIdx, true);
9507 }
9508 }
9509 }
9510 }
9511 });
9512
9513
9514 // browser location hash management
9515 if( G.O.locationHash ) {
9516 // jQuery(window).bind( 'hashchange', function() {
9517 // ProcessLocationHash();
9518 // });
9519 jQuery(window).on('hashchange.nanogallery2.' + G.baseEltID, function() {ProcessLocationHash();} );
9520 }
9521
9522 // Page resize / orientation change
9523 jQuery(window).on('resize.nanogallery2.' + G.baseEltID + ' orientationChange.nanogallery2.' + G.baseEltID, debounce( ResizeWindowEvent, G.O.eventsDebounceDelay, false) );
9524
9525 // Event page scrolled
9526 jQuery(window).on('scroll.nanogallery2.' + G.baseEltID, debounce( OnScrollEvent, G.O.eventsDebounceDelay, false) );
9527
9528 // Debounced function to hide the toolbars on the viewer
9529 G.VOM.toolsHide = debounce( ViewerToolsHide, G.O.viewerHideToolsDelay, false );
9530
9531 // Keyboard management
9532 jQuery(document).keyup(function(e) {
9533 if( G.popup.isDisplayed ) {
9534 switch( e.keyCode) {
9535 case 27: // Esc key
9536 G.popup.close();
9537 break;
9538 }
9539 }
9540 else {
9541 if( G.VOM.viewerDisplayed ) {
9542 ViewerToolsUnHide();
9543 switch( e.keyCode) {
9544 case 27: // Escape key
9545 case 40: // DOWN
9546 CloseInternalViewer(G.VOM.currItemIdx);
9547 break;
9548 case 32: // SPACE
9549 case 13: // ENTER
9550 SlideshowToggle();
9551 break;
9552 case 38: // UP
9553 case 39: // RIGHT
9554 case 33: // PAGE UP
9555 DisplayNextMedia();
9556 break;
9557 case 37: // LEFT
9558 case 34: // PAGE DOWN
9559 DisplayPreviousMedia();
9560 break;
9561 case 35: // END
9562 case 36: // BEGIN
9563 }
9564 }
9565 }
9566 });
9567
9568 // mouse wheel to zoom in/out the image displayed in the internal lightbox
9569 jQuery(window).bind('mousewheel wheel', function(e){
9570 if( G.VOM.viewerDisplayed ) {
9571 var deltaY = 0;
9572 e.preventDefault();
9573
9574 if( ViewerZoomStart() ) {
9575 if (e.originalEvent.deltaY) { // FireFox 17+ (IE9+, Chrome 31+?)
9576 deltaY = e.originalEvent.deltaY;
9577 } else if (e.originalEvent.wheelDelta) {
9578 deltaY = -e.originalEvent.wheelDelta;
9579 }
9580 ViewerZoomIn( deltaY <= 0 ? true : false );
9581 }
9582 }
9583 });
9584
9585 // mouse mouse -> unhide lightbox toolbars
9586 jQuery(window).bind('mousemove', function(e){
9587 if( G.VOM.viewerDisplayed ) {
9588 debounce( ViewerToolsUnHide, 100, false )();
9589 }
9590 });
9591
9592 // fullscreen mode on/off --> internal lightbox
9593 if( ngscreenfull.enabled ) {
9594 // ngscreenfull.onchange(() => {
9595 ngscreenfull.onchange( function() {
9596 if( G.VOM.viewerDisplayed ) {
9597 if( ngscreenfull.isFullscreen ) {
9598 G.VOM.viewerIsFullscreen=true;
9599 G.VOM.$viewer.find('.fullscreenButton').html(G.O.icons.viewerFullscreenOff);
9600 }
9601 else {
9602 G.VOM.viewerIsFullscreen=false;
9603 G.VOM.$viewer.find('.fullscreenButton').html(G.O.icons.viewerFullscreenOn);
9604 }
9605 }
9606 });
9607 }
9608
9609 }
9610
9611 //----- Manage browser location hash (deep linking and browser back/forward)
9612 function ProcessLocationHash() {
9613
9614 // standard use case -> location hash processing
9615 if( !G.O.locationHash ) { return false; }
9616
9617 var curGal = '#nanogallery/' + G.baseEltID + '/',
9618 newLocationHash = location.hash;
9619 if( G.O.debugMode ) {
9620 console.log('------------------------ PROCESS LOCATION HASH');
9621 console.log('newLocationHash1: ' +newLocationHash);
9622 console.log('G.locationHashLastUsed: ' +G.locationHashLastUsed);
9623 }
9624
9625 if( newLocationHash == '' ) {
9626 // if( G.GOM.lastDisplayedIdx != -1 ) {
9627 if( G.locationHashLastUsed !== '' ) {
9628 // back button and no hash --> display first album
9629 if( G.O.debugMode ) { console.log('display root album' ); }
9630 G.locationHashLastUsed = '';
9631 if( G.O.debugMode ) { console.log('new3 G.locationHashLastUsed: ' + G.locationHashLastUsed); }
9632 DisplayAlbum('', '0');
9633 return true;
9634 }
9635 }
9636
9637 if( newLocationHash == G.locationHashLastUsed ) { return; }
9638
9639 if( newLocationHash.indexOf(curGal) == 0 ) {
9640 // item IDs detected
9641 var IDs=parseIDs( newLocationHash.substring(curGal.length) );
9642 if( IDs.imageID != '0' ) {
9643 if( G.O.debugMode ) { console.log('display image: ' + IDs.albumID +'-'+ IDs.imageID ); }
9644 DisplayPhoto( IDs.imageID, IDs.albumID );
9645 return true;
9646 }
9647 else {
9648 if( G.O.debugMode ) { console.log('display album: ' + IDs.albumID ); }
9649 DisplayAlbum( '-1', IDs.albumID );
9650 return true;
9651 }
9652 }
9653
9654 return false;
9655 }
9656
9657 //---- Set a new browser location hash
9658 function SetLocationHash(albumID, imageID ) {
9659 if( !G.O.locationHash ) { return false; }
9660
9661 if( G.O.debugMode ) {
9662 console.log('------------------------ SET LOCATION HASH');
9663 }
9664
9665 if( imageID == '' && (albumID == '-1' || albumID == '0' || G.O.album == albumID ) ) {
9666 // root album level --> do not set top.location.hash if not already set
9667 if( location.hash != '' ) {
9668 // try to clear the hash if set
9669 if ("pushState" in history) {
9670 history.pushState("", document.title, window.location.pathname + window.location.search);
9671 }
9672 else {
9673 location.hash='';
9674 }
9675 }
9676 G.locationHashLastUsed='';
9677 if( G.O.debugMode ) { console.log('new2 G.locationHashLastUsed: '+G.locationHashLastUsed); }
9678 return;
9679 }
9680
9681 var newLocationHash='#'+'nanogallery/'+G.baseEltID+'/'+ albumID;
9682 if( imageID != '' ) {
9683 newLocationHash+='/'+imageID;
9684 }
9685
9686 var lH=location.hash;
9687 if( G.O.debugMode ) {
9688 console.log('newLocationHash2: '+newLocationHash);
9689 console.log('location.hash: '+lH);
9690 }
9691
9692 G.locationHashLastUsed=newLocationHash;
9693 if( G.O.debugMode ) { console.log('new G.locationHashLastUsed: '+G.locationHashLastUsed); }
9694
9695 if( lH == '' || lH != newLocationHash ) {
9696 // G.locationHashLastUsed='#'+newLocationHash;
9697 try {
9698 top.location.hash=newLocationHash;
9699 }
9700 catch(e) {
9701 // location hash is not supported by current browser --> disable the option
9702 G.O.locationHash=false;
9703 }
9704 }
9705 }
9706
9707
9708 function ResizeWindowEvent() {
9709 CacheViewport();
9710
9711 if( G.VOM.viewerDisplayed ) {
9712 ResizeInternalViewer();
9713 }
9714 else {
9715 if( G.galleryResizeEventEnabled ) {
9716 var nw = RetrieveCurWidth();
9717 if( G.GOM.albumIdx != -1 &&
9718 ( G.tn.settings.height[G.GOM.curNavLevel][G.GOM.curWidth] != G.tn.settings.height[G.GOM.curNavLevel][nw] ||
9719 G.tn.settings.width[G.GOM.curNavLevel][G.GOM.curWidth] != G.tn.settings.width[G.GOM.curNavLevel][nw] ) ) {
9720 // do not use settings.getH() / settings.getW()
9721 // thumbnail size changed --> render the gallery with the new sizes
9722 G.GOM.curWidth = nw;
9723 //G.layout.SetEngine();
9724 G.GOM.pagination.currentPage = 0;
9725 GalleryRender( G.GOM.albumIdx );
9726 }
9727 else {
9728 GalleryResize();
9729 }
9730 }
9731 }
9732 }
9733
9734
9735 // Depreciated - if gallery currently refreshed (G.galleryResizeEventEnabled=false), page may be scrolled but it will not be refreshed again
9736 function OnScrollEvent_OLD() {
9737 if( !G.VOM.viewerDisplayed ) {
9738 if( G.galleryResizeEventEnabled ) {
9739 GalleryResize();
9740 }
9741 return;
9742 }
9743 }
9744
9745
9746 function OnScrollEvent() {
9747 if( !G.VOM.viewerDisplayed ) {
9748 GalleryResizeOnScrollEvent();
9749 }
9750 }
9751 // the gallery may currently be refreshed, so ensure that at the end of the refresh, the gallery is refreshed again because the page may have been scrolled in the meantime
9752 function GalleryResizeOnScrollEvent() {
9753 if( G.galleryResizeEventEnabled == false) {
9754 window.setTimeout(GalleryResizeOnScrollEvent, 10); // check again in 10ms
9755 } else {
9756 GalleryResize();
9757 }
9758 }
9759
9760
9761
9762 // I18N : define text translations
9763 function i18n() {
9764
9765 // browser language
9766 G.i18nLang = (navigator.language || navigator.userLanguage).toUpperCase();
9767 if( G.i18nLang === 'UNDEFINED') { G.i18nLang=''; }
9768
9769 var llang=-('_'+G.i18nLang).length;
9770
9771 if( toType(G.O.i18n) == 'object' ){
9772
9773 for( var key in G.O.i18n ) {
9774 //var value = G.O.i18n[key];
9775 var s=key.substr(llang);
9776 if( s == ('_'+G.i18nLang) ) {
9777 G.i18nTranslations[key.substr(0,key.length-s.length)]=G.O.i18n[key];
9778 }
9779 else {
9780 G.i18nTranslations[key]=G.O.i18n[key];
9781 }
9782 }
9783 }
9784 }
9785
9786 function GetI18nItem( item, property ) {
9787 var s='';
9788 if( G.i18nLang != '' ) {
9789 if( item[property+'_'+G.i18nLang] !== undefined && item[property+'_'+G.i18nLang].length>0 ) {
9790 s=item[property+'_'+G.i18nLang];
9791 return s;
9792 }
9793 }
9794 s=item[property];
9795 return s;
9796 }
9797
9798
9799 function RetrieveCurWidth() {
9800 var vpW = G.GOM.cache.viewport.w;
9801
9802 if( G.O.breakpointSizeSM > 0 && vpW < G.O.breakpointSizeSM) { return 'xs'; }
9803 if( G.O.breakpointSizeME > 0 && vpW < G.O.breakpointSizeME) { return 'sm'; }
9804 if( G.O.breakpointSizeLA > 0 && vpW < G.O.breakpointSizeLA) { return 'me'; }
9805 if( G.O.breakpointSizeXL > 0 && vpW < G.O.breakpointSizeXL) { return 'la'; }
9806
9807 return 'xl';
9808 }
9809
9810
9811 /** @function browserNotification */
9812 function browserNotification() {
9813 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>';
9814 m += '&nbsp;&nbsp;&nbsp; <a href="http://www.google.com/chrome/?hl=en-US)">Chrome</a><br>';
9815 m += '&nbsp;&nbsp;&nbsp; <a href="http://www.mozilla.com/firefox/)">Firefox</a><br>';
9816 m += '&nbsp;&nbsp;&nbsp; <a href="http://www.microsoft.com/windows/internet-explorer/default.aspx">Internet Explorer</a><br>';
9817 m += '&nbsp;&nbsp;&nbsp; <a href="http://www.apple.com/safari/download/">Safari</a>';
9818 NanoAlert(G, m, false);
9819 }
9820
9821 // 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
9822 function FirstSupportedPropertyName(prefixedPropertyNames) {
9823 var tempDiv = document.createElement("div");
9824 for (var i = 0; i < prefixedPropertyNames.length; ++i) {
9825 if (typeof tempDiv.style[prefixedPropertyNames[i]] != 'undefined')
9826 return prefixedPropertyNames[i];
9827 }
9828 return null;
9829 }
9830
9831
9832
9833
9834 }
9835
9836
9837
9838//##########################################################################################################################
9839//## imagesLoaded ##########################################################################################################
9840//##########################################################################################################################
9841
9842// external module EMBEDED in nanogallery
9843// NGY BUILD:
9844// replace "imagesLoaded" with "ngimagesLoaded"
9845// replace "ImagesLoaded" with "ngImagesLoaded"
9846// replace "EvEmitter" with "ngEvEmitter"
9847// replace "var $ = window.jQuery" with "var $ = jQuery;"
9848// 2x (global.ngEvEmitter and window.ngimagesLoaded = f...)ignore package manager and set browser global
9849
9850
9851
9852/*!
9853 * imagesLoaded PACKAGED v4.1.1
9854 * JavaScript is all like "You images are done yet or what?"
9855 * MIT License
9856 */
9857
9858/**
9859 * EvEmitter v1.0.3
9860 * Lil' event emitter
9861 * MIT License
9862 */
9863
9864
9865/* jshint unused: true, undef: true, strict: true */
9866
9867( function( global, factory ) {
9868 // universal module definition
9869 /* jshint strict: false */ /* globals define, module, window */
9870// if ( typeof define == 'function' && define.amd ) {
9871 // AMD - RequireJS
9872// define( 'ev-emitter/ev-emitter',factory );
9873// } else if ( typeof module == 'object' && module.exports ) {
9874 // CommonJS - Browserify, Webpack
9875// module.exports = factory();
9876// } else {
9877 // Browser globals
9878 global.ngEvEmitter = factory();
9879// }
9880
9881}( typeof window != 'undefined' ? window : this, function() {
9882
9883
9884
9885function ngEvEmitter() {}
9886
9887var proto = ngEvEmitter.prototype;
9888
9889proto.on = function( eventName, listener ) {
9890 if ( !eventName || !listener ) {
9891 return;
9892 }
9893 // set events hash
9894 var events = this._events = this._events || {};
9895 // set listeners array
9896 var listeners = events[ eventName ] = events[ eventName ] || [];
9897 // only add once
9898 if ( listeners.indexOf( listener ) == -1 ) {
9899 listeners.push( listener );
9900 }
9901
9902 return this;
9903};
9904
9905proto.once = function( eventName, listener ) {
9906 if ( !eventName || !listener ) {
9907 return;
9908 }
9909 // add event
9910 this.on( eventName, listener );
9911 // set once flag
9912 // set onceEvents hash
9913 var onceEvents = this._onceEvents = this._onceEvents || {};
9914 // set onceListeners object
9915 var onceListeners = onceEvents[ eventName ] = onceEvents[ eventName ] || {};
9916 // set flag
9917 onceListeners[ listener ] = true;
9918
9919 return this;
9920};
9921
9922proto.off = function( eventName, listener ) {
9923 var listeners = this._events && this._events[ eventName ];
9924 if ( !listeners || !listeners.length ) {
9925 return;
9926 }
9927 var index = listeners.indexOf( listener );
9928 if ( index != -1 ) {
9929 listeners.splice( index, 1 );
9930 }
9931
9932 return this;
9933};
9934
9935proto.emitEvent = function( eventName, args ) {
9936 var listeners = this._events && this._events[ eventName ];
9937 if ( !listeners || !listeners.length ) {
9938 return;
9939 }
9940 var i = 0;
9941 var listener = listeners[i];
9942 args = args || [];
9943 // once stuff
9944 var onceListeners = this._onceEvents && this._onceEvents[ eventName ];
9945
9946 while ( listener ) {
9947 var isOnce = onceListeners && onceListeners[ listener ];
9948 if ( isOnce ) {
9949 // remove listener
9950 // remove before trigger to prevent recursion
9951 this.off( eventName, listener );
9952 // unset once flag
9953 delete onceListeners[ listener ];
9954 }
9955 // trigger listener
9956 listener.apply( this, args );
9957 // get next listener
9958 i += isOnce ? 0 : 1;
9959 listener = listeners[i];
9960 }
9961
9962 return this;
9963};
9964
9965return ngEvEmitter;
9966
9967}));
9968
9969/*!
9970 * ngimagesLoaded v4.1.1
9971 * JavaScript is all like "You images are done yet or what?"
9972 * MIT License
9973 */
9974
9975( function( window, factory ) { 'use strict';
9976 // universal module definition
9977
9978 /*global define: false, module: false, require: false */
9979
9980// if ( typeof define == 'function' && define.amd ) {
9981 // AMD
9982// define( [
9983// 'ev-emitter/ev-emitter'
9984// ], function( ngEvEmitter ) {
9985// return factory( window, ngEvEmitter );
9986// });
9987// } else if ( typeof module == 'object' && module.exports ) {
9988 // CommonJS
9989// module.exports = factory(
9990// window,
9991// require('ev-emitter')
9992// );
9993// } else {
9994 // browser global
9995 window.ngimagesLoaded = factory(
9996 window,
9997 window.ngEvEmitter
9998 );
9999 //}
10000
10001})( window,
10002
10003// -------------------------- factory -------------------------- //
10004
10005function factory( window, ngEvEmitter ) {
10006
10007
10008
10009// var $ = window.jQuery;
10010var $ = jQuery;
10011var console = window.console;
10012
10013// -------------------------- helpers -------------------------- //
10014
10015// extend objects
10016function extend( a, b ) {
10017 for ( var prop in b ) {
10018 a[ prop ] = b[ prop ];
10019 }
10020 return a;
10021}
10022
10023// turn element or nodeList into an array
10024function makeArray( obj ) {
10025 var ary = [];
10026 if ( Array.isArray( obj ) ) {
10027 // use object if already an array
10028 ary = obj;
10029 } else if ( typeof obj.length == 'number' ) {
10030 // convert nodeList to array
10031 for ( var i=0; i < obj.length; i++ ) {
10032 ary.push( obj[i] );
10033 }
10034 } else {
10035 // array of single index
10036 ary.push( obj );
10037 }
10038 return ary;
10039}
10040
10041// -------------------------- ngimagesLoaded -------------------------- //
10042
10043/**
10044 * @param {Array, Element, NodeList, String} elem
10045 * @param {Object or Function} options - if function, use as callback
10046 * @param {Function} onAlways - callback function
10047 */
10048function ngImagesLoaded( elem, options, onAlways ) {
10049 // coerce ngImagesLoaded() without new, to be new ngImagesLoaded()
10050 if ( !( this instanceof ngImagesLoaded ) ) {
10051 return new ngImagesLoaded( elem, options, onAlways );
10052 }
10053 // use elem as selector string
10054 if ( typeof elem == 'string' ) {
10055 elem = document.querySelectorAll( elem );
10056 }
10057
10058 this.elements = makeArray( elem );
10059 this.options = extend( {}, this.options );
10060
10061 if ( typeof options == 'function' ) {
10062 onAlways = options;
10063 } else {
10064 extend( this.options, options );
10065 }
10066
10067 if ( onAlways ) {
10068 this.on( 'always', onAlways );
10069 }
10070
10071 this.getImages();
10072
10073 if ( $ ) {
10074 // add jQuery Deferred object
10075 this.jqDeferred = new $.Deferred();
10076 }
10077
10078 // HACK check async to allow time to bind listeners
10079 setTimeout( function() {
10080 this.check();
10081 }.bind( this ));
10082}
10083
10084ngImagesLoaded.prototype = Object.create( ngEvEmitter.prototype );
10085
10086ngImagesLoaded.prototype.options = {};
10087
10088ngImagesLoaded.prototype.getImages = function() {
10089 this.images = [];
10090
10091 // filter & find items if we have an item selector
10092 this.elements.forEach( this.addElementImages, this );
10093};
10094
10095/**
10096 * @param {Node} element
10097 */
10098ngImagesLoaded.prototype.addElementImages = function( elem ) {
10099 // filter siblings
10100 if ( elem.nodeName == 'IMG' ) {
10101 this.addImage( elem );
10102 }
10103 // get background image on element
10104 if ( this.options.background === true ) {
10105 this.addElementBackgroundImages( elem );
10106 }
10107
10108 // find children
10109 // no non-element nodes, #143
10110 var nodeType = elem.nodeType;
10111 if ( !nodeType || !elementNodeTypes[ nodeType ] ) {
10112 return;
10113 }
10114 var childImgs = elem.querySelectorAll('img');
10115 // concat childElems to filterFound array
10116 for ( var i=0; i < childImgs.length; i++ ) {
10117 var img = childImgs[i];
10118 this.addImage( img );
10119 }
10120
10121 // get child background images
10122 if ( typeof this.options.background == 'string' ) {
10123 var children = elem.querySelectorAll( this.options.background );
10124 for ( i=0; i < children.length; i++ ) {
10125 var child = children[i];
10126 this.addElementBackgroundImages( child );
10127 }
10128 }
10129};
10130
10131var elementNodeTypes = {
10132 1: true,
10133 9: true,
10134 11: true
10135};
10136
10137ngImagesLoaded.prototype.addElementBackgroundImages = function( elem ) {
10138 var style = getComputedStyle( elem );
10139 if ( !style ) {
10140 // Firefox returns null if in a hidden iframe https://bugzil.la/548397
10141 return;
10142 }
10143 // get url inside url("...")
10144 var reURL = /url\((['"])?(.*?)\1\)/gi;
10145 var matches = reURL.exec( style.backgroundImage );
10146 while ( matches !== null ) {
10147 var url = matches && matches[2];
10148 if ( url ) {
10149 this.addBackground( url, elem );
10150 }
10151 matches = reURL.exec( style.backgroundImage );
10152 }
10153};
10154
10155/**
10156 * @param {Image} img
10157 */
10158ngImagesLoaded.prototype.addImage = function( img ) {
10159 var loadingImage = new LoadingImage( img );
10160 this.images.push( loadingImage );
10161};
10162
10163ngImagesLoaded.prototype.addBackground = function( url, elem ) {
10164 var background = new Background( url, elem );
10165 this.images.push( background );
10166};
10167
10168ngImagesLoaded.prototype.check = function() {
10169 var _this = this;
10170 this.progressedCount = 0;
10171 this.hasAnyBroken = false;
10172 // complete if no images
10173 if ( !this.images.length ) {
10174 this.complete();
10175 return;
10176 }
10177
10178 function onProgress( image, elem, message ) {
10179 // HACK - Chrome triggers event before object properties have changed. #83
10180 setTimeout( function() {
10181 _this.progress( image, elem, message );
10182 });
10183 }
10184
10185 this.images.forEach( function( loadingImage ) {
10186 loadingImage.once( 'progress', onProgress );
10187 loadingImage.check();
10188 });
10189};
10190
10191ngImagesLoaded.prototype.progress = function( image, elem, message ) {
10192 this.progressedCount++;
10193 this.hasAnyBroken = this.hasAnyBroken || !image.isLoaded;
10194 // progress event
10195 this.emitEvent( 'progress', [ this, image, elem ] );
10196 if ( this.jqDeferred && this.jqDeferred.notify ) {
10197 this.jqDeferred.notify( this, image );
10198 }
10199 // check if completed
10200 if ( this.progressedCount == this.images.length ) {
10201 this.complete();
10202 }
10203
10204 if ( this.options.debug && console ) {
10205 console.log( 'progress: ' + message, image, elem );
10206 }
10207};
10208
10209ngImagesLoaded.prototype.complete = function() {
10210 var eventName = this.hasAnyBroken ? 'fail' : 'done';
10211 this.isComplete = true;
10212 this.emitEvent( eventName, [ this ] );
10213 this.emitEvent( 'always', [ this ] );
10214 if ( this.jqDeferred ) {
10215 var jqMethod = this.hasAnyBroken ? 'reject' : 'resolve';
10216 this.jqDeferred[ jqMethod ]( this );
10217 }
10218};
10219
10220// -------------------------- -------------------------- //
10221
10222function LoadingImage( img ) {
10223 this.img = img;
10224}
10225
10226LoadingImage.prototype = Object.create( ngEvEmitter.prototype );
10227
10228LoadingImage.prototype.check = function() {
10229 // If complete is true and browser supports natural sizes,
10230 // try to check for image status manually.
10231 var isComplete = this.getIsImageComplete();
10232 if ( isComplete ) {
10233 // report based on naturalWidth
10234 this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' );
10235 return;
10236 }
10237
10238 // If none of the checks above matched, simulate loading on detached element.
10239 this.proxyImage = new Image();
10240 this.proxyImage.addEventListener( 'load', this );
10241 this.proxyImage.addEventListener( 'error', this );
10242 // bind to image as well for Firefox. #191
10243 this.img.addEventListener( 'load', this );
10244 this.img.addEventListener( 'error', this );
10245 this.proxyImage.src = this.img.src;
10246};
10247
10248LoadingImage.prototype.getIsImageComplete = function() {
10249 return this.img.complete && this.img.naturalWidth !== undefined;
10250};
10251
10252LoadingImage.prototype.confirm = function( isLoaded, message ) {
10253 this.isLoaded = isLoaded;
10254 this.emitEvent( 'progress', [ this, this.img, message ] );
10255};
10256
10257// ----- events ----- //
10258
10259// trigger specified handler for event type
10260LoadingImage.prototype.handleEvent = function( event ) {
10261 var method = 'on' + event.type;
10262 if ( this[ method ] ) {
10263 this[ method ]( event );
10264 }
10265};
10266
10267LoadingImage.prototype.onload = function() {
10268 this.confirm( true, 'onload' );
10269 this.unbindEvents();
10270};
10271
10272LoadingImage.prototype.onerror = function() {
10273 this.confirm( false, 'onerror' );
10274 this.unbindEvents();
10275};
10276
10277LoadingImage.prototype.unbindEvents = function() {
10278 this.proxyImage.removeEventListener( 'load', this );
10279 this.proxyImage.removeEventListener( 'error', this );
10280 this.img.removeEventListener( 'load', this );
10281 this.img.removeEventListener( 'error', this );
10282};
10283
10284// -------------------------- Background -------------------------- //
10285
10286function Background( url, element ) {
10287 this.url = url;
10288 this.element = element;
10289 this.img = new Image();
10290}
10291
10292// inherit LoadingImage prototype
10293Background.prototype = Object.create( LoadingImage.prototype );
10294
10295Background.prototype.check = function() {
10296 this.img.addEventListener( 'load', this );
10297 this.img.addEventListener( 'error', this );
10298 this.img.src = this.url;
10299 // check if image is already complete
10300 var isComplete = this.getIsImageComplete();
10301 if ( isComplete ) {
10302 this.confirm( this.img.naturalWidth !== 0, 'naturalWidth' );
10303 this.unbindEvents();
10304 }
10305};
10306
10307Background.prototype.unbindEvents = function() {
10308 this.img.removeEventListener( 'load', this );
10309 this.img.removeEventListener( 'error', this );
10310};
10311
10312Background.prototype.confirm = function( isLoaded, message ) {
10313 this.isLoaded = isLoaded;
10314 this.emitEvent( 'progress', [ this, this.element, message ] );
10315};
10316
10317// -------------------------- jQuery -------------------------- //
10318
10319ngImagesLoaded.makeJQueryPlugin = function( jQuery ) {
10320 jQuery = jQuery || window.jQuery;
10321 if ( !jQuery ) {
10322 return;
10323 }
10324 // set local variable
10325 $ = jQuery;
10326 // $().ngimagesLoaded()
10327 $.fn.ngimagesLoaded = function( options, callback ) {
10328 var instance = new ngImagesLoaded( this, options, callback );
10329 return instance.jqDeferred.promise( $(this) );
10330 };
10331};
10332// try making plugin
10333ngImagesLoaded.makeJQueryPlugin();
10334
10335// -------------------------- -------------------------- //
10336
10337return ngImagesLoaded;
10338
10339});
10340
10341
10342
10343//##########################################################################################################################
10344//## screenfull.js #########################################################################################################
10345//##########################################################################################################################
10346
10347// screenfull.js
10348// v4.0.1
10349// by sindresorhus - https://github.com/sindresorhus
10350// from: https://github.com/sindresorhus/screenfull.js
10351
10352// external module embeded in nanogallery
10353// NGY BUILD:
10354// replace "screenfull" with "ngscreenfull"
10355//
10356
10357(function () {
10358 'use strict';
10359
10360 var document = typeof window !== 'undefined' && typeof window.document !== 'undefined' ? window.document : {};
10361 var isCommonjs = typeof module !== 'undefined' && module.exports;
10362 var keyboardAllowed = typeof Element !== 'undefined' && 'ALLOW_KEYBOARD_INPUT' in Element;
10363
10364 var fn = (function () {
10365 var val;
10366
10367 var fnMap = [
10368 [
10369 'requestFullscreen',
10370 'exitFullscreen',
10371 'fullscreenElement',
10372 'fullscreenEnabled',
10373 'fullscreenchange',
10374 'fullscreenerror'
10375 ],
10376 // New WebKit
10377 [
10378 'webkitRequestFullscreen',
10379 'webkitExitFullscreen',
10380 'webkitFullscreenElement',
10381 'webkitFullscreenEnabled',
10382 'webkitfullscreenchange',
10383 'webkitfullscreenerror'
10384
10385 ],
10386 // Old WebKit (Safari 5.1)
10387 [
10388 'webkitRequestFullScreen',
10389 'webkitCancelFullScreen',
10390 'webkitCurrentFullScreenElement',
10391 'webkitCancelFullScreen',
10392 'webkitfullscreenchange',
10393 'webkitfullscreenerror'
10394
10395 ],
10396 [
10397 'mozRequestFullScreen',
10398 'mozCancelFullScreen',
10399 'mozFullScreenElement',
10400 'mozFullScreenEnabled',
10401 'mozfullscreenchange',
10402 'mozfullscreenerror'
10403 ],
10404 [
10405 'msRequestFullscreen',
10406 'msExitFullscreen',
10407 'msFullscreenElement',
10408 'msFullscreenEnabled',
10409 'MSFullscreenChange',
10410 'MSFullscreenError'
10411 ]
10412 ];
10413
10414 var i = 0;
10415 var l = fnMap.length;
10416 var ret = {};
10417
10418 for (; i < l; i++) {
10419 val = fnMap[i];
10420 if (val && val[1] in document) {
10421 for (i = 0; i < val.length; i++) {
10422 ret[fnMap[0][i]] = val[i];
10423 }
10424 return ret;
10425 }
10426 }
10427
10428 return false;
10429 })();
10430
10431 var eventNameMap = {
10432 change: fn.fullscreenchange,
10433 error: fn.fullscreenerror
10434 };
10435
10436 var ngscreenfull = {
10437 request: function (elem) {
10438 return new Promise(function (resolve) {
10439 var request = fn.requestFullscreen;
10440
10441 var onFullScreenEntered = function () {
10442 this.off('change', onFullScreenEntered);
10443 resolve();
10444 }.bind(this);
10445
10446 elem = elem || document.documentElement;
10447
10448 // Work around Safari 5.1 bug: reports support for
10449 // keyboard in fullscreen even though it doesn't.
10450 // Browser sniffing, since the alternative with
10451 // setTimeout is even worse.
10452 if (/ Version\/5\.1(?:\.\d+)? Safari\//.test(navigator.userAgent)) {
10453 elem[request]();
10454 } else {
10455 elem[request](keyboardAllowed ? Element.ALLOW_KEYBOARD_INPUT : {});
10456 }
10457
10458 this.on('change', onFullScreenEntered);
10459 }.bind(this));
10460 },
10461 exit: function () {
10462 return new Promise(function (resolve) {
10463 if (!this.isFullscreen) {
10464 resolve();
10465 return;
10466 }
10467
10468 var onFullScreenExit = function () {
10469 this.off('change', onFullScreenExit);
10470 resolve();
10471 }.bind(this);
10472
10473 document[fn.exitFullscreen]();
10474
10475 this.on('change', onFullScreenExit);
10476 }.bind(this));
10477 },
10478 toggle: function (elem) {
10479 return this.isFullscreen ? this.exit() : this.request(elem);
10480 },
10481 onchange: function (callback) {
10482 this.on('change', callback);
10483 },
10484 onerror: function (callback) {
10485 this.on('error', callback);
10486 },
10487 on: function (event, callback) {
10488 var eventName = eventNameMap[event];
10489 if (eventName) {
10490 document.addEventListener(eventName, callback, false);
10491 }
10492 },
10493 off: function (event, callback) {
10494 var eventName = eventNameMap[event];
10495 if (eventName) {
10496 document.removeEventListener(eventName, callback, false);
10497 }
10498 },
10499 raw: fn
10500 };
10501
10502 if (!fn) {
10503 if (isCommonjs) {
10504 module.exports = false;
10505 } else {
10506 window.ngscreenfull = false;
10507 }
10508
10509 return;
10510 }
10511
10512 Object.defineProperties(ngscreenfull, {
10513 isFullscreen: {
10514 get: function () {
10515 return Boolean(document[fn.fullscreenElement]);
10516 }
10517 },
10518 element: {
10519 enumerable: true,
10520 get: function () {
10521 return document[fn.fullscreenElement];
10522 }
10523 },
10524 enabled: {
10525 enumerable: true,
10526 get: function () {
10527 // Coerce to boolean in case of old WebKit
10528 return Boolean(document[fn.fullscreenEnabled]);
10529 }
10530 }
10531 });
10532
10533 if (isCommonjs) {
10534 module.exports = ngscreenfull;
10535 } else {
10536 window.ngscreenfull = ngscreenfull;
10537 }
10538})();
10539
10540
10541
10542//##########################################################################################################################
10543//## Shifty ################################################################################################################
10544//##########################################################################################################################
10545
10546 /*!
10547 * Shifty
10548 * By Jeremy Kahn - jeremyckahn@gmail.com
10549 */
10550
10551// external module EMBEDED in nanogallery
10552// NGY BUILD:
10553//
10554// replace "Tweenable" with "NGTweenable"
10555// replace "define.amd" with "define.amdDISABLED"
10556/* shifty - v1.5.3 - 2016-11-29 - http://jeremyckahn.github.io/shifty */
10557;(function () {
10558 var root = this || Function('return this')();
10559
10560/**
10561 * Shifty Core
10562 * By Jeremy Kahn - jeremyckahn@gmail.com
10563 */
10564
10565var NGTweenable = (function () {
10566
10567 'use strict';
10568
10569 // Aliases that get defined later in this function
10570 var formula;
10571
10572 // CONSTANTS
10573 var DEFAULT_SCHEDULE_FUNCTION;
10574 var DEFAULT_EASING = 'linear';
10575 var DEFAULT_DURATION = 500;
10576 var UPDATE_TIME = 1000 / 60;
10577
10578 var _now = Date.now
10579 ? Date.now
10580 : function () {return +new Date();};
10581
10582 var now = typeof SHIFTY_DEBUG_NOW !== 'undefined' ? SHIFTY_DEBUG_NOW : _now;
10583
10584 if (typeof window !== 'undefined') {
10585 // requestAnimationFrame() shim by Paul Irish (modified for Shifty)
10586 // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
10587 DEFAULT_SCHEDULE_FUNCTION = window.requestAnimationFrame
10588 || window.webkitRequestAnimationFrame
10589 || window.oRequestAnimationFrame
10590 || window.msRequestAnimationFrame
10591 || (window.mozCancelRequestAnimationFrame
10592 && window.mozRequestAnimationFrame)
10593 || setTimeout;
10594 } else {
10595 DEFAULT_SCHEDULE_FUNCTION = setTimeout;
10596 }
10597
10598 function noop () {
10599 // NOOP!
10600 }
10601
10602 /**
10603 * Handy shortcut for doing a for-in loop. This is not a "normal" each
10604 * function, it is optimized for Shifty. The iterator function only receives
10605 * the property name, not the value.
10606 * @param {Object} obj
10607 * @param {Function(string)} fn
10608 * @private
10609 */
10610 function each (obj, fn) {
10611 var key;
10612 for (key in obj) {
10613 if (Object.hasOwnProperty.call(obj, key)) {
10614 fn(key);
10615 }
10616 }
10617 }
10618
10619 /**
10620 * Perform a shallow copy of Object properties.
10621 * @param {Object} targetObject The object to copy into
10622 * @param {Object} srcObject The object to copy from
10623 * @return {Object} A reference to the augmented `targetObj` Object
10624 * @private
10625 */
10626 function shallowCopy (targetObj, srcObj) {
10627 each(srcObj, function (prop) {
10628 targetObj[prop] = srcObj[prop];
10629 });
10630
10631 return targetObj;
10632 }
10633
10634 /**
10635 * Copies each property from src onto target, but only if the property to
10636 * copy to target is undefined.
10637 * @param {Object} target Missing properties in this Object are filled in
10638 * @param {Object} src
10639 * @private
10640 */
10641 function defaults (target, src) {
10642 each(src, function (prop) {
10643 if (typeof target[prop] === 'undefined') {
10644 target[prop] = src[prop];
10645 }
10646 });
10647 }
10648
10649 /**
10650 * Calculates the interpolated tween values of an Object for a given
10651 * timestamp.
10652 * @param {Number} forPosition The position to compute the state for.
10653 * @param {Object} currentState Current state properties.
10654 * @param {Object} originalState: The original state properties the Object is
10655 * tweening from.
10656 * @param {Object} targetState: The destination state properties the Object
10657 * is tweening to.
10658 * @param {number} duration: The length of the tween in milliseconds.
10659 * @param {number} timestamp: The UNIX epoch time at which the tween began.
10660 * @param {Object} easing: This Object's keys must correspond to the keys in
10661 * targetState.
10662 * @private
10663 */
10664 function tweenProps (forPosition, currentState, originalState, targetState,
10665 duration, timestamp, easing) {
10666 var normalizedPosition =
10667 forPosition < timestamp ? 0 : (forPosition - timestamp) / duration;
10668
10669
10670 var prop;
10671 var easingObjectProp;
10672 var easingFn;
10673 for (prop in currentState) {
10674 if (currentState.hasOwnProperty(prop)) {
10675 easingObjectProp = easing[prop];
10676 easingFn = typeof easingObjectProp === 'function'
10677 ? easingObjectProp
10678 : formula[easingObjectProp];
10679
10680 currentState[prop] = tweenProp(
10681 originalState[prop],
10682 targetState[prop],
10683 easingFn,
10684 normalizedPosition
10685 );
10686 }
10687 }
10688
10689 return currentState;
10690 }
10691
10692 /**
10693 * Tweens a single property.
10694 * @param {number} start The value that the tween started from.
10695 * @param {number} end The value that the tween should end at.
10696 * @param {Function} easingFunc The easing curve to apply to the tween.
10697 * @param {number} position The normalized position (between 0.0 and 1.0) to
10698 * calculate the midpoint of 'start' and 'end' against.
10699 * @return {number} The tweened value.
10700 * @private
10701 */
10702 function tweenProp (start, end, easingFunc, position) {
10703 return start + (end - start) * easingFunc(position);
10704 }
10705
10706 /**
10707 * Applies a filter to NGTweenable instance.
10708 * @param {NGTweenable} tweenable The `NGTweenable` instance to call the filter
10709 * upon.
10710 * @param {String} filterName The name of the filter to apply.
10711 * @private
10712 */
10713 function applyFilter (tweenable, filterName) {
10714 var filters = NGTweenable.prototype.filter;
10715 var args = tweenable._filterArgs;
10716
10717 each(filters, function (name) {
10718 if (typeof filters[name][filterName] !== 'undefined') {
10719 filters[name][filterName].apply(tweenable, args);
10720 }
10721 });
10722 }
10723
10724 var timeoutHandler_endTime;
10725 var timeoutHandler_currentTime;
10726 var timeoutHandler_isEnded;
10727 var timeoutHandler_offset;
10728 /**
10729 * Handles the update logic for one step of a tween.
10730 * @param {NGTweenable} tweenable
10731 * @param {number} timestamp
10732 * @param {number} delay
10733 * @param {number} duration
10734 * @param {Object} currentState
10735 * @param {Object} originalState
10736 * @param {Object} targetState
10737 * @param {Object} easing
10738 * @param {Function(Object, *, number)} step
10739 * @param {Function(Function,number)}} schedule
10740 * @param {number=} opt_currentTimeOverride Needed for accurate timestamp in
10741 * NGTweenable#seek.
10742 * @private
10743 */
10744 function timeoutHandler (tweenable, timestamp, delay, duration, currentState,
10745 originalState, targetState, easing, step, schedule,
10746 opt_currentTimeOverride) {
10747
10748 timeoutHandler_endTime = timestamp + delay + duration;
10749
10750 timeoutHandler_currentTime =
10751 Math.min(opt_currentTimeOverride || now(), timeoutHandler_endTime);
10752
10753 timeoutHandler_isEnded =
10754 timeoutHandler_currentTime >= timeoutHandler_endTime;
10755
10756 timeoutHandler_offset = duration - (
10757 timeoutHandler_endTime - timeoutHandler_currentTime);
10758
10759 if (tweenable.isPlaying()) {
10760 if (timeoutHandler_isEnded) {
10761 step(targetState, tweenable._attachment, timeoutHandler_offset);
10762 tweenable.stop(true);
10763 } else {
10764 tweenable._scheduleId =
10765 schedule(tweenable._timeoutHandler, UPDATE_TIME);
10766
10767 applyFilter(tweenable, 'beforeTween');
10768
10769 // If the animation has not yet reached the start point (e.g., there was
10770 // delay that has not yet completed), just interpolate the starting
10771 // position of the tween.
10772 if (timeoutHandler_currentTime < (timestamp + delay)) {
10773 tweenProps(1, currentState, originalState, targetState, 1, 1, easing);
10774 } else {
10775 tweenProps(timeoutHandler_currentTime, currentState, originalState,
10776 targetState, duration, timestamp + delay, easing);
10777 }
10778
10779 applyFilter(tweenable, 'afterTween');
10780
10781 step(currentState, tweenable._attachment, timeoutHandler_offset);
10782 }
10783 }
10784 }
10785
10786
10787 /**
10788 * Creates a usable easing Object from a string, a function or another easing
10789 * Object. If `easing` is an Object, then this function clones it and fills
10790 * in the missing properties with `"linear"`.
10791 * @param {Object.<string|Function>} fromTweenParams
10792 * @param {Object|string|Function} easing
10793 * @return {Object.<string|Function>}
10794 * @private
10795 */
10796 function composeEasingObject (fromTweenParams, easing) {
10797 var composedEasing = {};
10798 var typeofEasing = typeof easing;
10799
10800 if (typeofEasing === 'string' || typeofEasing === 'function') {
10801 each(fromTweenParams, function (prop) {
10802 composedEasing[prop] = easing;
10803 });
10804 } else {
10805 each(fromTweenParams, function (prop) {
10806 if (!composedEasing[prop]) {
10807 composedEasing[prop] = easing[prop] || DEFAULT_EASING;
10808 }
10809 });
10810 }
10811
10812 return composedEasing;
10813 }
10814
10815 /**
10816 * NGTweenable constructor.
10817 * @class NGTweenable
10818 * @param {Object=} opt_initialState The values that the initial tween should
10819 * start at if a `from` object is not provided to `{{#crossLink
10820 * "NGTweenable/tween:method"}}{{/crossLink}}` or `{{#crossLink
10821 * "NGTweenable/setConfig:method"}}{{/crossLink}}`.
10822 * @param {Object=} opt_config Configuration object to be passed to
10823 * `{{#crossLink "NGTweenable/setConfig:method"}}{{/crossLink}}`.
10824 * @module NGTweenable
10825 * @constructor
10826 */
10827 function NGTweenable (opt_initialState, opt_config) {
10828 this._currentState = opt_initialState || {};
10829 this._configured = false;
10830 this._scheduleFunction = DEFAULT_SCHEDULE_FUNCTION;
10831
10832 // To prevent unnecessary calls to setConfig do not set default
10833 // configuration here. Only set default configuration immediately before
10834 // tweening if none has been set.
10835 if (typeof opt_config !== 'undefined') {
10836 this.setConfig(opt_config);
10837 }
10838 }
10839
10840 /**
10841 * Configure and start a tween.
10842 * @method tween
10843 * @param {Object=} opt_config Configuration object to be passed to
10844 * `{{#crossLink "NGTweenable/setConfig:method"}}{{/crossLink}}`.
10845 * @chainable
10846 */
10847 NGTweenable.prototype.tween = function (opt_config) {
10848 if (this._isTweening) {
10849 return this;
10850 }
10851
10852 // Only set default config if no configuration has been set previously and
10853 // none is provided now.
10854 if (opt_config !== undefined || !this._configured) {
10855 this.setConfig(opt_config);
10856 }
10857
10858 this._timestamp = now();
10859 this._start(this.get(), this._attachment);
10860 return this.resume();
10861 };
10862
10863 /**
10864 * Configure a tween that will start at some point in the future.
10865 *
10866 * @method setConfig
10867 * @param {Object} config The following values are valid:
10868 * - __from__ (_Object=_): Starting position. If omitted, `{{#crossLink
10869 * "NGTweenable/get:method"}}get(){{/crossLink}}` is used.
10870 * - __to__ (_Object=_): Ending position.
10871 * - __duration__ (_number=_): How many milliseconds to animate for.
10872 * - __delay__ (_delay=_): How many milliseconds to wait before starting the
10873 * tween.
10874 * - __start__ (_Function(Object, *)_): Function to execute when the tween
10875 * begins. Receives the state of the tween as the first parameter and
10876 * `attachment` as the second parameter.
10877 * - __step__ (_Function(Object, *, number)_): Function to execute on every
10878 * tick. Receives `{{#crossLink
10879 * "NGTweenable/get:method"}}get(){{/crossLink}}` as the first parameter,
10880 * `attachment` as the second parameter, and the time elapsed since the
10881 * start of the tween as the third. This function is not called on the
10882 * final step of the animation, but `finish` is.
10883 * - __finish__ (_Function(Object, *)_): Function to execute upon tween
10884 * completion. Receives the state of the tween as the first parameter and
10885 * `attachment` as the second parameter.
10886 * - __easing__ (_Object.<string|Function>|string|Function=_): Easing curve
10887 * name(s) or function(s) to use for the tween.
10888 * - __attachment__ (_*_): Cached value that is passed to the
10889 * `step`/`start`/`finish` methods.
10890 * @chainable
10891 */
10892 NGTweenable.prototype.setConfig = function (config) {
10893 config = config || {};
10894 this._configured = true;
10895
10896 // Attach something to this NGTweenable instance (e.g.: a DOM element, an
10897 // object, a string, etc.);
10898 this._attachment = config.attachment;
10899
10900 // Init the internal state
10901 this._pausedAtTime = null;
10902 this._scheduleId = null;
10903 this._delay = config.delay || 0;
10904 this._start = config.start || noop;
10905 this._step = config.step || noop;
10906 this._finish = config.finish || noop;
10907 this._duration = config.duration || DEFAULT_DURATION;
10908 this._currentState = shallowCopy({}, config.from || this.get());
10909 this._originalState = this.get();
10910 this._targetState = shallowCopy({}, config.to || this.get());
10911
10912 var self = this;
10913 this._timeoutHandler = function () {
10914 timeoutHandler(self,
10915 self._timestamp,
10916 self._delay,
10917 self._duration,
10918 self._currentState,
10919 self._originalState,
10920 self._targetState,
10921 self._easing,
10922 self._step,
10923 self._scheduleFunction
10924 );
10925 };
10926
10927 // Aliases used below
10928 var currentState = this._currentState;
10929 var targetState = this._targetState;
10930
10931 // Ensure that there is always something to tween to.
10932 defaults(targetState, currentState);
10933
10934 this._easing = composeEasingObject(
10935 currentState, config.easing || DEFAULT_EASING);
10936
10937 this._filterArgs =
10938 [currentState, this._originalState, targetState, this._easing];
10939
10940 applyFilter(this, 'tweenCreated');
10941 return this;
10942 };
10943
10944 /**
10945 * @method get
10946 * @return {Object} The current state.
10947 */
10948 NGTweenable.prototype.get = function () {
10949 return shallowCopy({}, this._currentState);
10950 };
10951
10952 /**
10953 * @method set
10954 * @param {Object} state The current state.
10955 */
10956 NGTweenable.prototype.set = function (state) {
10957 this._currentState = state;
10958 };
10959
10960 /**
10961 * Pause a tween. Paused tweens can be resumed from the point at which they
10962 * were paused. This is different from `{{#crossLink
10963 * "NGTweenable/stop:method"}}{{/crossLink}}`, as that method
10964 * causes a tween to start over when it is resumed.
10965 * @method pause
10966 * @chainable
10967 */
10968 NGTweenable.prototype.pause = function () {
10969 this._pausedAtTime = now();
10970 this._isPaused = true;
10971 return this;
10972 };
10973
10974 /**
10975 * Resume a paused tween.
10976 * @method resume
10977 * @chainable
10978 */
10979 NGTweenable.prototype.resume = function () {
10980 if (this._isPaused) {
10981 this._timestamp += now() - this._pausedAtTime;
10982 }
10983
10984 this._isPaused = false;
10985 this._isTweening = true;
10986
10987 this._timeoutHandler();
10988
10989 return this;
10990 };
10991
10992 /**
10993 * Move the state of the animation to a specific point in the tween's
10994 * timeline. If the animation is not running, this will cause the `step`
10995 * handlers to be called.
10996 * @method seek
10997 * @param {millisecond} millisecond The millisecond of the animation to seek
10998 * to. This must not be less than `0`.
10999 * @chainable
11000 */
11001 NGTweenable.prototype.seek = function (millisecond) {
11002 millisecond = Math.max(millisecond, 0);
11003 var currentTime = now();
11004
11005 if ((this._timestamp + millisecond) === 0) {
11006 return this;
11007 }
11008
11009 this._timestamp = currentTime - millisecond;
11010
11011 if (!this.isPlaying()) {
11012 this._isTweening = true;
11013 this._isPaused = false;
11014
11015 // If the animation is not running, call timeoutHandler to make sure that
11016 // any step handlers are run.
11017 timeoutHandler(this,
11018 this._timestamp,
11019 this._delay,
11020 this._duration,
11021 this._currentState,
11022 this._originalState,
11023 this._targetState,
11024 this._easing,
11025 this._step,
11026 this._scheduleFunction,
11027 currentTime
11028 );
11029
11030 this.pause();
11031 }
11032
11033 return this;
11034 };
11035
11036 /**
11037 * Stops and cancels a tween.
11038 * @param {boolean=} gotoEnd If `false` or omitted, the tween just stops at
11039 * its current state, and the `finish` handler is not invoked. If `true`,
11040 * the tweened object's values are instantly set to the target values, and
11041 * `finish` is invoked.
11042 * @method stop
11043 * @chainable
11044 */
11045 NGTweenable.prototype.stop = function (gotoEnd) {
11046 this._isTweening = false;
11047 this._isPaused = false;
11048 this._timeoutHandler = noop;
11049
11050 (root.cancelAnimationFrame ||
11051 root.webkitCancelAnimationFrame ||
11052 root.oCancelAnimationFrame ||
11053 root.msCancelAnimationFrame ||
11054 root.mozCancelRequestAnimationFrame ||
11055 root.clearTimeout)(this._scheduleId);
11056
11057 if (gotoEnd) {
11058 applyFilter(this, 'beforeTween');
11059 tweenProps(
11060 1,
11061 this._currentState,
11062 this._originalState,
11063 this._targetState,
11064 1,
11065 0,
11066 this._easing
11067 );
11068 applyFilter(this, 'afterTween');
11069 applyFilter(this, 'afterTweenEnd');
11070 this._finish.call(this, this._currentState, this._attachment);
11071 }
11072
11073 return this;
11074 };
11075
11076 /**
11077 * @method isPlaying
11078 * @return {boolean} Whether or not a tween is running.
11079 */
11080 NGTweenable.prototype.isPlaying = function () {
11081 return this._isTweening && !this._isPaused;
11082 };
11083
11084 /**
11085 * Set a custom schedule function.
11086 *
11087 * If a custom function is not set,
11088 * [`requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/Web/API/window.requestAnimationFrame)
11089 * is used if available, otherwise
11090 * [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/Window.setTimeout)
11091 * is used.
11092 * @method setScheduleFunction
11093 * @param {Function(Function,number)} scheduleFunction The function to be
11094 * used to schedule the next frame to be rendered.
11095 */
11096 NGTweenable.prototype.setScheduleFunction = function (scheduleFunction) {
11097 this._scheduleFunction = scheduleFunction;
11098 };
11099
11100 /**
11101 * `delete` all "own" properties. Call this when the `NGTweenable` instance
11102 * is no longer needed to free memory.
11103 * @method dispose
11104 */
11105 NGTweenable.prototype.dispose = function () {
11106 var prop;
11107 for (prop in this) {
11108 if (this.hasOwnProperty(prop)) {
11109 delete this[prop];
11110 }
11111 }
11112 };
11113
11114 /**
11115 * Filters are used for transforming the properties of a tween at various
11116 * points in a NGTweenable's life cycle. See the README for more info on this.
11117 * @private
11118 */
11119 NGTweenable.prototype.filter = {};
11120
11121 /**
11122 * This object contains all of the tweens available to Shifty. It is
11123 * extensible - simply attach properties to the `NGTweenable.prototype.formula`
11124 * Object following the same format as `linear`.
11125 *
11126 * `pos` should be a normalized `number` (between 0 and 1).
11127 * @property formula
11128 * @type {Object(function)}
11129 */
11130 NGTweenable.prototype.formula = {
11131 linear: function (pos) {
11132 return pos;
11133 }
11134 };
11135
11136 formula = NGTweenable.prototype.formula;
11137
11138 shallowCopy(NGTweenable, {
11139 'now': now
11140 ,'each': each
11141 ,'tweenProps': tweenProps
11142 ,'tweenProp': tweenProp
11143 ,'applyFilter': applyFilter
11144 ,'shallowCopy': shallowCopy
11145 ,'defaults': defaults
11146 ,'composeEasingObject': composeEasingObject
11147 });
11148
11149 // `root` is provided in the intro/outro files.
11150
11151 // A hook used for unit testing.
11152 if (typeof SHIFTY_DEBUG_NOW === 'function') {
11153 root.timeoutHandler = timeoutHandler;
11154 }
11155
11156 // Bootstrap NGTweenable appropriately for the environment.
11157 if (typeof exports === 'object') {
11158 // CommonJS
11159 module.exports = NGTweenable;
11160 } else if (typeof define === 'function' && define.amdDISABLED) {
11161 // AMD
11162 define(function () {return NGTweenable;});
11163 } else if (typeof root.NGTweenable === 'undefined') {
11164 // Browser: Make `NGTweenable` globally accessible.
11165 root.NGTweenable = NGTweenable;
11166 }
11167
11168 return NGTweenable;
11169
11170} ());
11171
11172/*!
11173 * All equations are adapted from Thomas Fuchs'
11174 * [Scripty2](https://github.com/madrobby/scripty2/blob/master/src/effects/transitions/penner.js).
11175 *
11176 * Based on Easing Equations (c) 2003 [Robert
11177 * Penner](http://www.robertpenner.com/), all rights reserved. This work is
11178 * [subject to terms](http://www.robertpenner.com/easing_terms_of_use.html).
11179 */
11180
11181/*!
11182 * TERMS OF USE - EASING EQUATIONS
11183 * Open source under the BSD License.
11184 * Easing Equations (c) 2003 Robert Penner, all rights reserved.
11185 */
11186
11187;(function () {
11188
11189 NGTweenable.shallowCopy(NGTweenable.prototype.formula, {
11190 easeInQuad: function (pos) {
11191 return Math.pow(pos, 2);
11192 },
11193
11194 easeOutQuad: function (pos) {
11195 return -(Math.pow((pos - 1), 2) - 1);
11196 },
11197
11198 easeInOutQuad: function (pos) {
11199 if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,2);}
11200 return -0.5 * ((pos -= 2) * pos - 2);
11201 },
11202
11203 easeInCubic: function (pos) {
11204 return Math.pow(pos, 3);
11205 },
11206
11207 easeOutCubic: function (pos) {
11208 return (Math.pow((pos - 1), 3) + 1);
11209 },
11210
11211 easeInOutCubic: function (pos) {
11212 if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,3);}
11213 return 0.5 * (Math.pow((pos - 2),3) + 2);
11214 },
11215
11216 easeInQuart: function (pos) {
11217 return Math.pow(pos, 4);
11218 },
11219
11220 easeOutQuart: function (pos) {
11221 return -(Math.pow((pos - 1), 4) - 1);
11222 },
11223
11224 easeInOutQuart: function (pos) {
11225 if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,4);}
11226 return -0.5 * ((pos -= 2) * Math.pow(pos,3) - 2);
11227 },
11228
11229 easeInQuint: function (pos) {
11230 return Math.pow(pos, 5);
11231 },
11232
11233 easeOutQuint: function (pos) {
11234 return (Math.pow((pos - 1), 5) + 1);
11235 },
11236
11237 easeInOutQuint: function (pos) {
11238 if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,5);}
11239 return 0.5 * (Math.pow((pos - 2),5) + 2);
11240 },
11241
11242 easeInSine: function (pos) {
11243 return -Math.cos(pos * (Math.PI / 2)) + 1;
11244 },
11245
11246 easeOutSine: function (pos) {
11247 return Math.sin(pos * (Math.PI / 2));
11248 },
11249
11250 easeInOutSine: function (pos) {
11251 return (-0.5 * (Math.cos(Math.PI * pos) - 1));
11252 },
11253
11254 easeInExpo: function (pos) {
11255 return (pos === 0) ? 0 : Math.pow(2, 10 * (pos - 1));
11256 },
11257
11258 easeOutExpo: function (pos) {
11259 return (pos === 1) ? 1 : -Math.pow(2, -10 * pos) + 1;
11260 },
11261
11262 easeInOutExpo: function (pos) {
11263 if (pos === 0) {return 0;}
11264 if (pos === 1) {return 1;}
11265 if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(2,10 * (pos - 1));}
11266 return 0.5 * (-Math.pow(2, -10 * --pos) + 2);
11267 },
11268
11269 easeInCirc: function (pos) {
11270 return -(Math.sqrt(1 - (pos * pos)) - 1);
11271 },
11272
11273 easeOutCirc: function (pos) {
11274 return Math.sqrt(1 - Math.pow((pos - 1), 2));
11275 },
11276
11277 easeInOutCirc: function (pos) {
11278 if ((pos /= 0.5) < 1) {return -0.5 * (Math.sqrt(1 - pos * pos) - 1);}
11279 return 0.5 * (Math.sqrt(1 - (pos -= 2) * pos) + 1);
11280 },
11281
11282 easeOutBounce: function (pos) {
11283 if ((pos) < (1 / 2.75)) {
11284 return (7.5625 * pos * pos);
11285 } else if (pos < (2 / 2.75)) {
11286 return (7.5625 * (pos -= (1.5 / 2.75)) * pos + 0.75);
11287 } else if (pos < (2.5 / 2.75)) {
11288 return (7.5625 * (pos -= (2.25 / 2.75)) * pos + 0.9375);
11289 } else {
11290 return (7.5625 * (pos -= (2.625 / 2.75)) * pos + 0.984375);
11291 }
11292 },
11293
11294 easeInBack: function (pos) {
11295 var s = 1.70158;
11296 return (pos) * pos * ((s + 1) * pos - s);
11297 },
11298
11299 easeOutBack: function (pos) {
11300 var s = 1.70158;
11301 return (pos = pos - 1) * pos * ((s + 1) * pos + s) + 1;
11302 },
11303
11304 easeInOutBack: function (pos) {
11305 var s = 1.70158;
11306 if ((pos /= 0.5) < 1) {
11307 return 0.5 * (pos * pos * (((s *= (1.525)) + 1) * pos - s));
11308 }
11309 return 0.5 * ((pos -= 2) * pos * (((s *= (1.525)) + 1) * pos + s) + 2);
11310 },
11311
11312 elastic: function (pos) {
11313 // jshint maxlen:90
11314 return -1 * Math.pow(4,-8 * pos) * Math.sin((pos * 6 - 1) * (2 * Math.PI) / 2) + 1;
11315 },
11316
11317 swingFromTo: function (pos) {
11318 var s = 1.70158;
11319 return ((pos /= 0.5) < 1) ?
11320 0.5 * (pos * pos * (((s *= (1.525)) + 1) * pos - s)) :
11321 0.5 * ((pos -= 2) * pos * (((s *= (1.525)) + 1) * pos + s) + 2);
11322 },
11323
11324 swingFrom: function (pos) {
11325 var s = 1.70158;
11326 return pos * pos * ((s + 1) * pos - s);
11327 },
11328
11329 swingTo: function (pos) {
11330 var s = 1.70158;
11331 return (pos -= 1) * pos * ((s + 1) * pos + s) + 1;
11332 },
11333
11334 bounce: function (pos) {
11335 if (pos < (1 / 2.75)) {
11336 return (7.5625 * pos * pos);
11337 } else if (pos < (2 / 2.75)) {
11338 return (7.5625 * (pos -= (1.5 / 2.75)) * pos + 0.75);
11339 } else if (pos < (2.5 / 2.75)) {
11340 return (7.5625 * (pos -= (2.25 / 2.75)) * pos + 0.9375);
11341 } else {
11342 return (7.5625 * (pos -= (2.625 / 2.75)) * pos + 0.984375);
11343 }
11344 },
11345
11346 bouncePast: function (pos) {
11347 if (pos < (1 / 2.75)) {
11348 return (7.5625 * pos * pos);
11349 } else if (pos < (2 / 2.75)) {
11350 return 2 - (7.5625 * (pos -= (1.5 / 2.75)) * pos + 0.75);
11351 } else if (pos < (2.5 / 2.75)) {
11352 return 2 - (7.5625 * (pos -= (2.25 / 2.75)) * pos + 0.9375);
11353 } else {
11354 return 2 - (7.5625 * (pos -= (2.625 / 2.75)) * pos + 0.984375);
11355 }
11356 },
11357
11358 easeFromTo: function (pos) {
11359 if ((pos /= 0.5) < 1) {return 0.5 * Math.pow(pos,4);}
11360 return -0.5 * ((pos -= 2) * Math.pow(pos,3) - 2);
11361 },
11362
11363 easeFrom: function (pos) {
11364 return Math.pow(pos,4);
11365 },
11366
11367 easeTo: function (pos) {
11368 return Math.pow(pos,0.25);
11369 }
11370 });
11371
11372}());
11373
11374// jshint maxlen:100
11375/**
11376 * The Bezier magic in this file is adapted/copied almost wholesale from
11377 * [Scripty2](https://github.com/madrobby/scripty2/blob/master/src/effects/transitions/cubic-bezier.js),
11378 * which was adapted from Apple code (which probably came from
11379 * [here](http://opensource.apple.com/source/WebCore/WebCore-955.66/platform/graphics/UnitBezier.h)).
11380 * Special thanks to Apple and Thomas Fuchs for much of this code.
11381 */
11382
11383/**
11384 * Copyright (c) 2006 Apple Computer, Inc. All rights reserved.
11385 *
11386 * Redistribution and use in source and binary forms, with or without
11387 * modification, are permitted provided that the following conditions are met:
11388 *
11389 * 1. Redistributions of source code must retain the above copyright notice,
11390 * this list of conditions and the following disclaimer.
11391 *
11392 * 2. Redistributions in binary form must reproduce the above copyright notice,
11393 * this list of conditions and the following disclaimer in the documentation
11394 * and/or other materials provided with the distribution.
11395 *
11396 * 3. Neither the name of the copyright holder(s) nor the names of any
11397 * contributors may be used to endorse or promote products derived from
11398 * this software without specific prior written permission.
11399 *
11400 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
11401 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
11402 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
11403 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
11404 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
11405 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
11406 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
11407 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
11408 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
11409 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
11410 * POSSIBILITY OF SUCH DAMAGE.
11411 */
11412;(function () {
11413 // port of webkit cubic bezier handling by http://www.netzgesta.de/dev/
11414 function cubicBezierAtTime(t,p1x,p1y,p2x,p2y,duration) {
11415 var ax = 0,bx = 0,cx = 0,ay = 0,by = 0,cy = 0;
11416 function sampleCurveX(t) {
11417 return ((ax * t + bx) * t + cx) * t;
11418 }
11419 function sampleCurveY(t) {
11420 return ((ay * t + by) * t + cy) * t;
11421 }
11422 function sampleCurveDerivativeX(t) {
11423 return (3.0 * ax * t + 2.0 * bx) * t + cx;
11424 }
11425 function solveEpsilon(duration) {
11426 return 1.0 / (200.0 * duration);
11427 }
11428 function solve(x,epsilon) {
11429 return sampleCurveY(solveCurveX(x, epsilon));
11430 }
11431 function fabs(n) {
11432 if (n >= 0) {
11433 return n;
11434 } else {
11435 return 0 - n;
11436 }
11437 }
11438 function solveCurveX(x, epsilon) {
11439 var t0,t1,t2,x2,d2,i;
11440 for (t2 = x, i = 0; i < 8; i++) {
11441 x2 = sampleCurveX(t2) - x;
11442 if (fabs(x2) < epsilon) {
11443 return t2;
11444 }
11445 d2 = sampleCurveDerivativeX(t2);
11446 if (fabs(d2) < 1e-6) {
11447 break;
11448 }
11449 t2 = t2 - x2 / d2;
11450 }
11451 t0 = 0.0;
11452 t1 = 1.0;
11453 t2 = x;
11454 if (t2 < t0) {
11455 return t0;
11456 }
11457 if (t2 > t1) {
11458 return t1;
11459 }
11460 while (t0 < t1) {
11461 x2 = sampleCurveX(t2);
11462 if (fabs(x2 - x) < epsilon) {
11463 return t2;
11464 }
11465 if (x > x2) {
11466 t0 = t2;
11467 }else {
11468 t1 = t2;
11469 }
11470 t2 = (t1 - t0) * 0.5 + t0;
11471 }
11472 return t2; // Failure.
11473 }
11474 cx = 3.0 * p1x;
11475 bx = 3.0 * (p2x - p1x) - cx;
11476 ax = 1.0 - cx - bx;
11477 cy = 3.0 * p1y;
11478 by = 3.0 * (p2y - p1y) - cy;
11479 ay = 1.0 - cy - by;
11480 return solve(t, solveEpsilon(duration));
11481 }
11482 /**
11483 * getCubicBezierTransition(x1, y1, x2, y2) -> Function
11484 *
11485 * Generates a transition easing function that is compatible
11486 * with WebKit's CSS transitions `-webkit-transition-timing-function`
11487 * CSS property.
11488 *
11489 * The W3C has more information about CSS3 transition timing functions:
11490 * http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag
11491 *
11492 * @param {number} x1
11493 * @param {number} y1
11494 * @param {number} x2
11495 * @param {number} y2
11496 * @return {function}
11497 * @private
11498 */
11499 function getCubicBezierTransition (x1, y1, x2, y2) {
11500 return function (pos) {
11501 return cubicBezierAtTime(pos,x1,y1,x2,y2,1);
11502 };
11503 }
11504 // End ported code
11505
11506 /**
11507 * Create a Bezier easing function and attach it to `{{#crossLink
11508 * "NGTweenable/formula:property"}}NGTweenable#formula{{/crossLink}}`. This
11509 * function gives you total control over the easing curve. Matthew Lein's
11510 * [Ceaser](http://matthewlein.com/ceaser/) is a useful tool for visualizing
11511 * the curves you can make with this function.
11512 * @method setBezierFunction
11513 * @param {string} name The name of the easing curve. Overwrites the old
11514 * easing function on `{{#crossLink
11515 * "NGTweenable/formula:property"}}NGTweenable#formula{{/crossLink}}` if it
11516 * exists.
11517 * @param {number} x1
11518 * @param {number} y1
11519 * @param {number} x2
11520 * @param {number} y2
11521 * @return {function} The easing function that was attached to
11522 * NGTweenable.prototype.formula.
11523 */
11524 NGTweenable.setBezierFunction = function (name, x1, y1, x2, y2) {
11525 var cubicBezierTransition = getCubicBezierTransition(x1, y1, x2, y2);
11526 cubicBezierTransition.displayName = name;
11527 cubicBezierTransition.x1 = x1;
11528 cubicBezierTransition.y1 = y1;
11529 cubicBezierTransition.x2 = x2;
11530 cubicBezierTransition.y2 = y2;
11531
11532 return NGTweenable.prototype.formula[name] = cubicBezierTransition;
11533 };
11534
11535
11536 /**
11537 * `delete` an easing function from `{{#crossLink
11538 * "NGTweenable/formula:property"}}NGTweenable#formula{{/crossLink}}`. Be
11539 * careful with this method, as it `delete`s whatever easing formula matches
11540 * `name` (which means you can delete standard Shifty easing functions).
11541 * @method unsetBezierFunction
11542 * @param {string} name The name of the easing function to delete.
11543 * @return {function}
11544 */
11545 NGTweenable.unsetBezierFunction = function (name) {
11546 delete NGTweenable.prototype.formula[name];
11547 };
11548
11549})();
11550
11551;(function () {
11552
11553 function getInterpolatedValues (
11554 from, current, targetState, position, easing, delay) {
11555 return NGTweenable.tweenProps(
11556 position, current, from, targetState, 1, delay, easing);
11557 }
11558
11559 // Fake a NGTweenable and patch some internals. This approach allows us to
11560 // skip uneccessary processing and object recreation, cutting down on garbage
11561 // collection pauses.
11562 var mockNGTweenable = new NGTweenable();
11563 mockNGTweenable._filterArgs = [];
11564
11565 /**
11566 * Compute the midpoint of two Objects. This method effectively calculates a
11567 * specific frame of animation that `{{#crossLink
11568 * "NGTweenable/tween:method"}}{{/crossLink}}` does many times over the course
11569 * of a full tween.
11570 *
11571 * var interpolatedValues = NGTweenable.interpolate({
11572 * width: '100px',
11573 * opacity: 0,
11574 * color: '#fff'
11575 * }, {
11576 * width: '200px',
11577 * opacity: 1,
11578 * color: '#000'
11579 * }, 0.5);
11580 *
11581 * console.log(interpolatedValues);
11582 * // {opacity: 0.5, width: "150px", color: "rgb(127,127,127)"}
11583 *
11584 * @static
11585 * @method interpolate
11586 * @param {Object} from The starting values to tween from.
11587 * @param {Object} targetState The ending values to tween to.
11588 * @param {number} position The normalized position value (between `0.0` and
11589 * `1.0`) to interpolate the values between `from` and `to` for. `from`
11590 * represents `0` and `to` represents `1`.
11591 * @param {Object.<string|Function>|string|Function} easing The easing
11592 * curve(s) to calculate the midpoint against. You can reference any easing
11593 * function attached to `NGTweenable.prototype.formula`, or provide the easing
11594 * function(s) directly. If omitted, this defaults to "linear".
11595 * @param {number=} opt_delay Optional delay to pad the beginning of the
11596 * interpolated tween with. This increases the range of `position` from (`0`
11597 * through `1`) to (`0` through `1 + opt_delay`). So, a delay of `0.5` would
11598 * increase all valid values of `position` to numbers between `0` and `1.5`.
11599 * @return {Object}
11600 */
11601 NGTweenable.interpolate = function (
11602 from, targetState, position, easing, opt_delay) {
11603
11604 var current = NGTweenable.shallowCopy({}, from);
11605 var delay = opt_delay || 0;
11606 var easingObject = NGTweenable.composeEasingObject(
11607 from, easing || 'linear');
11608
11609 mockNGTweenable.set({});
11610
11611 // Alias and reuse the _filterArgs array instead of recreating it.
11612 var filterArgs = mockNGTweenable._filterArgs;
11613 filterArgs.length = 0;
11614 filterArgs[0] = current;
11615 filterArgs[1] = from;
11616 filterArgs[2] = targetState;
11617 filterArgs[3] = easingObject;
11618
11619 // Any defined value transformation must be applied
11620 NGTweenable.applyFilter(mockNGTweenable, 'tweenCreated');
11621 NGTweenable.applyFilter(mockNGTweenable, 'beforeTween');
11622
11623 var interpolatedValues = getInterpolatedValues(
11624 from, current, targetState, position, easingObject, delay);
11625
11626 // Transform values back into their original format
11627 NGTweenable.applyFilter(mockNGTweenable, 'afterTween');
11628
11629 return interpolatedValues;
11630 };
11631
11632}());
11633
11634/**
11635 * This module adds string interpolation support to Shifty.
11636 *
11637 * The Token extension allows Shifty to tween numbers inside of strings. Among
11638 * other things, this allows you to animate CSS properties. For example, you
11639 * can do this:
11640 *
11641 * var tweenable = new NGTweenable();
11642 * tweenable.tween({
11643 * from: { transform: 'translateX(45px)' },
11644 * to: { transform: 'translateX(90xp)' }
11645 * });
11646 *
11647 * `translateX(45)` will be tweened to `translateX(90)`. To demonstrate:
11648 *
11649 * var tweenable = new NGTweenable();
11650 * tweenable.tween({
11651 * from: { transform: 'translateX(45px)' },
11652 * to: { transform: 'translateX(90px)' },
11653 * step: function (state) {
11654 * console.log(state.transform);
11655 * }
11656 * });
11657 *
11658 * The above snippet will log something like this in the console:
11659 *
11660 * translateX(60.3px)
11661 * ...
11662 * translateX(76.05px)
11663 * ...
11664 * translateX(90px)
11665 *
11666 * Another use for this is animating colors:
11667 *
11668 * var tweenable = new NGTweenable();
11669 * tweenable.tween({
11670 * from: { color: 'rgb(0,255,0)' },
11671 * to: { color: 'rgb(255,0,255)' },
11672 * step: function (state) {
11673 * console.log(state.color);
11674 * }
11675 * });
11676 *
11677 * The above snippet will log something like this:
11678 *
11679 * rgb(84,170,84)
11680 * ...
11681 * rgb(170,84,170)
11682 * ...
11683 * rgb(255,0,255)
11684 *
11685 * This extension also supports hexadecimal colors, in both long (`#ff00ff`)
11686 * and short (`#f0f`) forms. Be aware that hexadecimal input values will be
11687 * converted into the equivalent RGB output values. This is done to optimize
11688 * for performance.
11689 *
11690 * var tweenable = new NGTweenable();
11691 * tweenable.tween({
11692 * from: { color: '#0f0' },
11693 * to: { color: '#f0f' },
11694 * step: function (state) {
11695 * console.log(state.color);
11696 * }
11697 * });
11698 *
11699 * This snippet will generate the same output as the one before it because
11700 * equivalent values were supplied (just in hexadecimal form rather than RGB):
11701 *
11702 * rgb(84,170,84)
11703 * ...
11704 * rgb(170,84,170)
11705 * ...
11706 * rgb(255,0,255)
11707 *
11708 * ## Easing support
11709 *
11710 * Easing works somewhat differently in the Token extension. This is because
11711 * some CSS properties have multiple values in them, and you might need to
11712 * tween each value along its own easing curve. A basic example:
11713 *
11714 * var tweenable = new NGTweenable();
11715 * tweenable.tween({
11716 * from: { transform: 'translateX(0px) translateY(0px)' },
11717 * to: { transform: 'translateX(100px) translateY(100px)' },
11718 * easing: { transform: 'easeInQuad' },
11719 * step: function (state) {
11720 * console.log(state.transform);
11721 * }
11722 * });
11723 *
11724 * The above snippet will create values like this:
11725 *
11726 * translateX(11.56px) translateY(11.56px)
11727 * ...
11728 * translateX(46.24px) translateY(46.24px)
11729 * ...
11730 * translateX(100px) translateY(100px)
11731 *
11732 * In this case, the values for `translateX` and `translateY` are always the
11733 * same for each step of the tween, because they have the same start and end
11734 * points and both use the same easing curve. We can also tween `translateX`
11735 * and `translateY` along independent curves:
11736 *
11737 * var tweenable = new NGTweenable();
11738 * tweenable.tween({
11739 * from: { transform: 'translateX(0px) translateY(0px)' },
11740 * to: { transform: 'translateX(100px) translateY(100px)' },
11741 * easing: { transform: 'easeInQuad bounce' },
11742 * step: function (state) {
11743 * console.log(state.transform);
11744 * }
11745 * });
11746 *
11747 * The above snippet will create values like this:
11748 *
11749 * translateX(10.89px) translateY(82.35px)
11750 * ...
11751 * translateX(44.89px) translateY(86.73px)
11752 * ...
11753 * translateX(100px) translateY(100px)
11754 *
11755 * `translateX` and `translateY` are not in sync anymore, because `easeInQuad`
11756 * was specified for `translateX` and `bounce` for `translateY`. Mixing and
11757 * matching easing curves can make for some interesting motion in your
11758 * animations.
11759 *
11760 * The order of the space-separated easing curves correspond the token values
11761 * they apply to. If there are more token values than easing curves listed,
11762 * the last easing curve listed is used.
11763 * @submodule NGTweenable.token
11764 */
11765
11766// token function is defined above only so that dox-foundation sees it as
11767// documentation and renders it. It is never used, and is optimized away at
11768// build time.
11769
11770;(function (NGTweenable) {
11771
11772 /**
11773 * @typedef {{
11774 * formatString: string
11775 * chunkNames: Array.<string>
11776 * }}
11777 * @private
11778 */
11779 var formatManifest;
11780
11781 // CONSTANTS
11782
11783 var R_NUMBER_COMPONENT = /(\d|\-|\.)/;
11784 var R_FORMAT_CHUNKS = /([^\-0-9\.]+)/g;
11785 var R_UNFORMATTED_VALUES = /[0-9.\-]+/g;
11786 var R_RGB = new RegExp(
11787 'rgb\\(' + R_UNFORMATTED_VALUES.source +
11788 (/,\s*/.source) + R_UNFORMATTED_VALUES.source +
11789 (/,\s*/.source) + R_UNFORMATTED_VALUES.source + '\\)', 'g');
11790 var R_RGB_PREFIX = /^.*\(/;
11791 var R_HEX = /#([0-9]|[a-f]){3,6}/gi;
11792 var VALUE_PLACEHOLDER = 'VAL';
11793
11794 // HELPERS
11795
11796 /**
11797 * @param {Array.number} rawValues
11798 * @param {string} prefix
11799 *
11800 * @return {Array.<string>}
11801 * @private
11802 */
11803 function getFormatChunksFrom (rawValues, prefix) {
11804 var accumulator = [];
11805
11806 var rawValuesLength = rawValues.length;
11807 var i;
11808
11809 for (i = 0; i < rawValuesLength; i++) {
11810 accumulator.push('_' + prefix + '_' + i);
11811 }
11812
11813 return accumulator;
11814 }
11815
11816 /**
11817 * @param {string} formattedString
11818 *
11819 * @return {string}
11820 * @private
11821 */
11822 function getFormatStringFrom (formattedString) {
11823 var chunks = formattedString.match(R_FORMAT_CHUNKS);
11824
11825 if (!chunks) {
11826 // chunks will be null if there were no tokens to parse in
11827 // formattedString (for example, if formattedString is '2'). Coerce
11828 // chunks to be useful here.
11829 chunks = ['', ''];
11830
11831 // If there is only one chunk, assume that the string is a number
11832 // followed by a token...
11833 // NOTE: This may be an unwise assumption.
11834 } else if (chunks.length === 1 ||
11835 // ...or if the string starts with a number component (".", "-", or a
11836 // digit)...
11837 formattedString.charAt(0).match(R_NUMBER_COMPONENT)) {
11838 // ...prepend an empty string here to make sure that the formatted number
11839 // is properly replaced by VALUE_PLACEHOLDER
11840 chunks.unshift('');
11841 }
11842
11843 return chunks.join(VALUE_PLACEHOLDER);
11844 }
11845
11846 /**
11847 * Convert all hex color values within a string to an rgb string.
11848 *
11849 * @param {Object} stateObject
11850 *
11851 * @return {Object} The modified obj
11852 * @private
11853 */
11854 function sanitizeObjectForHexProps (stateObject) {
11855 NGTweenable.each(stateObject, function (prop) {
11856 var currentProp = stateObject[prop];
11857
11858 if (typeof currentProp === 'string' && currentProp.match(R_HEX)) {
11859 stateObject[prop] = sanitizeHexChunksToRGB(currentProp);
11860 }
11861 });
11862 }
11863
11864 /**
11865 * @param {string} str
11866 *
11867 * @return {string}
11868 * @private
11869 */
11870 function sanitizeHexChunksToRGB (str) {
11871 return filterStringChunks(R_HEX, str, convertHexToRGB);
11872 }
11873
11874 /**
11875 * @param {string} hexString
11876 *
11877 * @return {string}
11878 * @private
11879 */
11880 function convertHexToRGB (hexString) {
11881 var rgbArr = hexToRGBArray(hexString);
11882 return 'rgb(' + rgbArr[0] + ',' + rgbArr[1] + ',' + rgbArr[2] + ')';
11883 }
11884
11885 var hexToRGBArray_returnArray = [];
11886 /**
11887 * Convert a hexadecimal string to an array with three items, one each for
11888 * the red, blue, and green decimal values.
11889 *
11890 * @param {string} hex A hexadecimal string.
11891 *
11892 * @returns {Array.<number>} The converted Array of RGB values if `hex` is a
11893 * valid string, or an Array of three 0's.
11894 * @private
11895 */
11896 function hexToRGBArray (hex) {
11897
11898 hex = hex.replace(/#/, '');
11899
11900 // If the string is a shorthand three digit hex notation, normalize it to
11901 // the standard six digit notation
11902 if (hex.length === 3) {
11903 hex = hex.split('');
11904 hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
11905 }
11906
11907 hexToRGBArray_returnArray[0] = hexToDec(hex.substr(0, 2));
11908 hexToRGBArray_returnArray[1] = hexToDec(hex.substr(2, 2));
11909 hexToRGBArray_returnArray[2] = hexToDec(hex.substr(4, 2));
11910
11911 return hexToRGBArray_returnArray;
11912 }
11913
11914 /**
11915 * Convert a base-16 number to base-10.
11916 *
11917 * @param {Number|String} hex The value to convert
11918 *
11919 * @returns {Number} The base-10 equivalent of `hex`.
11920 * @private
11921 */
11922 function hexToDec (hex) {
11923 return parseInt(hex, 16);
11924 }
11925
11926 /**
11927 * Runs a filter operation on all chunks of a string that match a RegExp
11928 *
11929 * @param {RegExp} pattern
11930 * @param {string} unfilteredString
11931 * @param {function(string)} filter
11932 *
11933 * @return {string}
11934 * @private
11935 */
11936 function filterStringChunks (pattern, unfilteredString, filter) {
11937 var pattenMatches = unfilteredString.match(pattern);
11938 var filteredString = unfilteredString.replace(pattern, VALUE_PLACEHOLDER);
11939
11940 if (pattenMatches) {
11941 var pattenMatchesLength = pattenMatches.length;
11942 var currentChunk;
11943
11944 for (var i = 0; i < pattenMatchesLength; i++) {
11945 currentChunk = pattenMatches.shift();
11946 filteredString = filteredString.replace(
11947 VALUE_PLACEHOLDER, filter(currentChunk));
11948 }
11949 }
11950
11951 return filteredString;
11952 }
11953
11954 /**
11955 * Check for floating point values within rgb strings and rounds them.
11956 *
11957 * @param {string} formattedString
11958 *
11959 * @return {string}
11960 * @private
11961 */
11962 function sanitizeRGBChunks (formattedString) {
11963 return filterStringChunks(R_RGB, formattedString, sanitizeRGBChunk);
11964 }
11965
11966 /**
11967 * @param {string} rgbChunk
11968 *
11969 * @return {string}
11970 * @private
11971 */
11972 function sanitizeRGBChunk (rgbChunk) {
11973 var numbers = rgbChunk.match(R_UNFORMATTED_VALUES);
11974 var numbersLength = numbers.length;
11975 var sanitizedString = rgbChunk.match(R_RGB_PREFIX)[0];
11976
11977 for (var i = 0; i < numbersLength; i++) {
11978 sanitizedString += parseInt(numbers[i], 10) + ',';
11979 }
11980
11981 sanitizedString = sanitizedString.slice(0, -1) + ')';
11982
11983 return sanitizedString;
11984 }
11985
11986 /**
11987 * @param {Object} stateObject
11988 *
11989 * @return {Object} An Object of formatManifests that correspond to
11990 * the string properties of stateObject
11991 * @private
11992 */
11993 function getFormatManifests (stateObject) {
11994 var manifestAccumulator = {};
11995
11996 NGTweenable.each(stateObject, function (prop) {
11997 var currentProp = stateObject[prop];
11998
11999 if (typeof currentProp === 'string') {
12000 var rawValues = getValuesFrom(currentProp);
12001
12002 manifestAccumulator[prop] = {
12003 'formatString': getFormatStringFrom(currentProp)
12004 ,'chunkNames': getFormatChunksFrom(rawValues, prop)
12005 };
12006 }
12007 });
12008
12009 return manifestAccumulator;
12010 }
12011
12012 /**
12013 * @param {Object} stateObject
12014 * @param {Object} formatManifests
12015 * @private
12016 */
12017 function expandFormattedProperties (stateObject, formatManifests) {
12018 NGTweenable.each(formatManifests, function (prop) {
12019 var currentProp = stateObject[prop];
12020 var rawValues = getValuesFrom(currentProp);
12021 var rawValuesLength = rawValues.length;
12022
12023 for (var i = 0; i < rawValuesLength; i++) {
12024 stateObject[formatManifests[prop].chunkNames[i]] = +rawValues[i];
12025 }
12026
12027 delete stateObject[prop];
12028 });
12029 }
12030
12031 /**
12032 * @param {Object} stateObject
12033 * @param {Object} formatManifests
12034 * @private
12035 */
12036 function collapseFormattedProperties (stateObject, formatManifests) {
12037 NGTweenable.each(formatManifests, function (prop) {
12038 var currentProp = stateObject[prop];
12039 var formatChunks = extractPropertyChunks(
12040 stateObject, formatManifests[prop].chunkNames);
12041 var valuesList = getValuesList(
12042 formatChunks, formatManifests[prop].chunkNames);
12043 currentProp = getFormattedValues(
12044 formatManifests[prop].formatString, valuesList);
12045 stateObject[prop] = sanitizeRGBChunks(currentProp);
12046 });
12047 }
12048
12049 /**
12050 * @param {Object} stateObject
12051 * @param {Array.<string>} chunkNames
12052 *
12053 * @return {Object} The extracted value chunks.
12054 * @private
12055 */
12056 function extractPropertyChunks (stateObject, chunkNames) {
12057 var extractedValues = {};
12058 var currentChunkName, chunkNamesLength = chunkNames.length;
12059
12060 for (var i = 0; i < chunkNamesLength; i++) {
12061 currentChunkName = chunkNames[i];
12062 extractedValues[currentChunkName] = stateObject[currentChunkName];
12063 delete stateObject[currentChunkName];
12064 }
12065
12066 return extractedValues;
12067 }
12068
12069 var getValuesList_accumulator = [];
12070 /**
12071 * @param {Object} stateObject
12072 * @param {Array.<string>} chunkNames
12073 *
12074 * @return {Array.<number>}
12075 * @private
12076 */
12077 function getValuesList (stateObject, chunkNames) {
12078 getValuesList_accumulator.length = 0;
12079 var chunkNamesLength = chunkNames.length;
12080
12081 for (var i = 0; i < chunkNamesLength; i++) {
12082 getValuesList_accumulator.push(stateObject[chunkNames[i]]);
12083 }
12084
12085 return getValuesList_accumulator;
12086 }
12087
12088 /**
12089 * @param {string} formatString
12090 * @param {Array.<number>} rawValues
12091 *
12092 * @return {string}
12093 * @private
12094 */
12095 function getFormattedValues (formatString, rawValues) {
12096 var formattedValueString = formatString;
12097 var rawValuesLength = rawValues.length;
12098
12099 for (var i = 0; i < rawValuesLength; i++) {
12100 formattedValueString = formattedValueString.replace(
12101 VALUE_PLACEHOLDER, +rawValues[i].toFixed(4));
12102 }
12103
12104 return formattedValueString;
12105 }
12106
12107 /**
12108 * Note: It's the duty of the caller to convert the Array elements of the
12109 * return value into numbers. This is a performance optimization.
12110 *
12111 * @param {string} formattedString
12112 *
12113 * @return {Array.<string>|null}
12114 * @private
12115 */
12116 function getValuesFrom (formattedString) {
12117 return formattedString.match(R_UNFORMATTED_VALUES);
12118 }
12119
12120 /**
12121 * @param {Object} easingObject
12122 * @param {Object} tokenData
12123 * @private
12124 */
12125 function expandEasingObject (easingObject, tokenData) {
12126 NGTweenable.each(tokenData, function (prop) {
12127 var currentProp = tokenData[prop];
12128 var chunkNames = currentProp.chunkNames;
12129 var chunkLength = chunkNames.length;
12130
12131 var easing = easingObject[prop];
12132 var i;
12133
12134 if (typeof easing === 'string') {
12135 var easingChunks = easing.split(' ');
12136 var lastEasingChunk = easingChunks[easingChunks.length - 1];
12137
12138 for (i = 0; i < chunkLength; i++) {
12139 easingObject[chunkNames[i]] = easingChunks[i] || lastEasingChunk;
12140 }
12141
12142 } else {
12143 for (i = 0; i < chunkLength; i++) {
12144 easingObject[chunkNames[i]] = easing;
12145 }
12146 }
12147
12148 delete easingObject[prop];
12149 });
12150 }
12151
12152 /**
12153 * @param {Object} easingObject
12154 * @param {Object} tokenData
12155 * @private
12156 */
12157 function collapseEasingObject (easingObject, tokenData) {
12158 NGTweenable.each(tokenData, function (prop) {
12159 var currentProp = tokenData[prop];
12160 var chunkNames = currentProp.chunkNames;
12161 var chunkLength = chunkNames.length;
12162
12163 var firstEasing = easingObject[chunkNames[0]];
12164 var typeofEasings = typeof firstEasing;
12165
12166 if (typeofEasings === 'string') {
12167 var composedEasingString = '';
12168
12169 for (var i = 0; i < chunkLength; i++) {
12170 composedEasingString += ' ' + easingObject[chunkNames[i]];
12171 delete easingObject[chunkNames[i]];
12172 }
12173
12174 easingObject[prop] = composedEasingString.substr(1);
12175 } else {
12176 easingObject[prop] = firstEasing;
12177 }
12178 });
12179 }
12180
12181 NGTweenable.prototype.filter.token = {
12182 'tweenCreated': function (currentState, fromState, toState, easingObject) {
12183 sanitizeObjectForHexProps(currentState);
12184 sanitizeObjectForHexProps(fromState);
12185 sanitizeObjectForHexProps(toState);
12186 this._tokenData = getFormatManifests(currentState);
12187 },
12188
12189 'beforeTween': function (currentState, fromState, toState, easingObject) {
12190 expandEasingObject(easingObject, this._tokenData);
12191 expandFormattedProperties(currentState, this._tokenData);
12192 expandFormattedProperties(fromState, this._tokenData);
12193 expandFormattedProperties(toState, this._tokenData);
12194 },
12195
12196 'afterTween': function (currentState, fromState, toState, easingObject) {
12197 collapseFormattedProperties(currentState, this._tokenData);
12198 collapseFormattedProperties(fromState, this._tokenData);
12199 collapseFormattedProperties(toState, this._tokenData);
12200 collapseEasingObject(easingObject, this._tokenData);
12201 }
12202 };
12203
12204} (NGTweenable));
12205
12206}).call(null);
12207
12208
12209
12210
12211//##########################################################################################################################
12212//## HAMMER.JS #############################################################################################################
12213//##########################################################################################################################
12214
12215// HAMMER.JS
12216
12217// external module EMBEDED in nanogallery
12218// NGY BUILD:
12219// replace "Hammer" with "NGHammer" (case sensitive)
12220// replace "var SUPPORT_POINTER_EVENTS = prefixed(window, 'PointerEvent') !== undefined;" with "var SUPPORT_POINTER_EVENTS = false;"
12221// replace "define.amd" with "define.amdDISABLED"
12222
12223
12224
12225/*! NGHammer.JS - v2.0.7 - 2016-04-22
12226 * http://hammerjs.github.io/
12227 *
12228 * Copyright (c) 2016 Jorik Tangelder;
12229 * Licensed under the MIT license */
12230(function(window, document, exportName, undefined) {
12231 'use strict';
12232
12233var VENDOR_PREFIXES = ['', 'webkit', 'Moz', 'MS', 'ms', 'o'];
12234var TEST_ELEMENT = document.createElement('div');
12235
12236var TYPE_FUNCTION = 'function';
12237
12238var round = Math.round;
12239var abs = Math.abs;
12240var now = Date.now;
12241
12242/**
12243 * set a timeout with a given scope
12244 * @param {Function} fn
12245 * @param {Number} timeout
12246 * @param {Object} context
12247 * @returns {number}
12248 */
12249function setTimeoutContext(fn, timeout, context) {
12250 return setTimeout(bindFn(fn, context), timeout);
12251}
12252
12253/**
12254 * if the argument is an array, we want to execute the fn on each entry
12255 * if it aint an array we don't want to do a thing.
12256 * this is used by all the methods that accept a single and array argument.
12257 * @param {*|Array} arg
12258 * @param {String} fn
12259 * @param {Object} [context]
12260 * @returns {Boolean}
12261 */
12262function invokeArrayArg(arg, fn, context) {
12263 if (Array.isArray(arg)) {
12264 each(arg, context[fn], context);
12265 return true;
12266 }
12267 return false;
12268}
12269
12270/**
12271 * walk objects and arrays
12272 * @param {Object} obj
12273 * @param {Function} iterator
12274 * @param {Object} context
12275 */
12276function each(obj, iterator, context) {
12277 var i;
12278
12279 if (!obj) {
12280 return;
12281 }
12282
12283 if (obj.forEach) {
12284 obj.forEach(iterator, context);
12285 } else if (obj.length !== undefined) {
12286 i = 0;
12287 while (i < obj.length) {
12288 iterator.call(context, obj[i], i, obj);
12289 i++;
12290 }
12291 } else {
12292 for (i in obj) {
12293 obj.hasOwnProperty(i) && iterator.call(context, obj[i], i, obj);
12294 }
12295 }
12296}
12297
12298/**
12299 * wrap a method with a deprecation warning and stack trace
12300 * @param {Function} method
12301 * @param {String} name
12302 * @param {String} message
12303 * @returns {Function} A new function wrapping the supplied method.
12304 */
12305function deprecate(method, name, message) {
12306 var deprecationMessage = 'DEPRECATED METHOD: ' + name + '\n' + message + ' AT \n';
12307 return function() {
12308 var e = new Error('get-stack-trace');
12309 var stack = e && e.stack ? e.stack.replace(/^[^\(]+?[\n$]/gm, '')
12310 .replace(/^\s+at\s+/gm, '')
12311 .replace(/^Object.<anonymous>\s*\(/gm, '{anonymous}()@') : 'Unknown Stack Trace';
12312
12313 var log = window.console && (window.console.warn || window.console.log);
12314 if (log) {
12315 log.call(window.console, deprecationMessage, stack);
12316 }
12317 return method.apply(this, arguments);
12318 };
12319}
12320
12321/**
12322 * extend object.
12323 * means that properties in dest will be overwritten by the ones in src.
12324 * @param {Object} target
12325 * @param {...Object} objects_to_assign
12326 * @returns {Object} target
12327 */
12328var assign;
12329if (typeof Object.assign !== 'function') {
12330 assign = function assign(target) {
12331 if (target === undefined || target === null) {
12332 throw new TypeError('Cannot convert undefined or null to object');
12333 }
12334
12335 var output = Object(target);
12336 for (var index = 1; index < arguments.length; index++) {
12337 var source = arguments[index];
12338 if (source !== undefined && source !== null) {
12339 for (var nextKey in source) {
12340 if (source.hasOwnProperty(nextKey)) {
12341 output[nextKey] = source[nextKey];
12342 }
12343 }
12344 }
12345 }
12346 return output;
12347 };
12348} else {
12349 assign = Object.assign;
12350}
12351
12352/**
12353 * extend object.
12354 * means that properties in dest will be overwritten by the ones in src.
12355 * @param {Object} dest
12356 * @param {Object} src
12357 * @param {Boolean} [merge=false]
12358 * @returns {Object} dest
12359 */
12360var extend = deprecate(function extend(dest, src, merge) {
12361 var keys = Object.keys(src);
12362 var i = 0;
12363 while (i < keys.length) {
12364 if (!merge || (merge && dest[keys[i]] === undefined)) {
12365 dest[keys[i]] = src[keys[i]];
12366 }
12367 i++;
12368 }
12369 return dest;
12370}, 'extend', 'Use `assign`.');
12371
12372/**
12373 * merge the values from src in the dest.
12374 * means that properties that exist in dest will not be overwritten by src
12375 * @param {Object} dest
12376 * @param {Object} src
12377 * @returns {Object} dest
12378 */
12379var merge = deprecate(function merge(dest, src) {
12380 return extend(dest, src, true);
12381}, 'merge', 'Use `assign`.');
12382
12383/**
12384 * simple class inheritance
12385 * @param {Function} child
12386 * @param {Function} base
12387 * @param {Object} [properties]
12388 */
12389function inherit(child, base, properties) {
12390 var baseP = base.prototype,
12391 childP;
12392
12393 childP = child.prototype = Object.create(baseP);
12394 childP.constructor = child;
12395 childP._super = baseP;
12396
12397 if (properties) {
12398 assign(childP, properties);
12399 }
12400}
12401
12402/**
12403 * simple function bind
12404 * @param {Function} fn
12405 * @param {Object} context
12406 * @returns {Function}
12407 */
12408function bindFn(fn, context) {
12409 return function boundFn() {
12410 return fn.apply(context, arguments);
12411 };
12412}
12413
12414/**
12415 * let a boolean value also be a function that must return a boolean
12416 * this first item in args will be used as the context
12417 * @param {Boolean|Function} val
12418 * @param {Array} [args]
12419 * @returns {Boolean}
12420 */
12421function boolOrFn(val, args) {
12422 if (typeof val == TYPE_FUNCTION) {
12423 return val.apply(args ? args[0] || undefined : undefined, args);
12424 }
12425 return val;
12426}
12427
12428/**
12429 * use the val2 when val1 is undefined
12430 * @param {*} val1
12431 * @param {*} val2
12432 * @returns {*}
12433 */
12434function ifUndefined(val1, val2) {
12435 return (val1 === undefined) ? val2 : val1;
12436}
12437
12438/**
12439 * addEventListener with multiple events at once
12440 * @param {EventTarget} target
12441 * @param {String} types
12442 * @param {Function} handler
12443 */
12444function addEventListeners(target, types, handler) {
12445 each(splitStr(types), function(type) {
12446 target.addEventListener(type, handler, false);
12447 });
12448}
12449
12450/**
12451 * removeEventListener with multiple events at once
12452 * @param {EventTarget} target
12453 * @param {String} types
12454 * @param {Function} handler
12455 */
12456function removeEventListeners(target, types, handler) {
12457 each(splitStr(types), function(type) {
12458 target.removeEventListener(type, handler, false);
12459 });
12460}
12461
12462/**
12463 * find if a node is in the given parent
12464 * @method hasParent
12465 * @param {HTMLElement} node
12466 * @param {HTMLElement} parent
12467 * @return {Boolean} found
12468 */
12469function hasParent(node, parent) {
12470 while (node) {
12471 if (node == parent) {
12472 return true;
12473 }
12474 node = node.parentNode;
12475 }
12476 return false;
12477}
12478
12479/**
12480 * small indexOf wrapper
12481 * @param {String} str
12482 * @param {String} find
12483 * @returns {Boolean} found
12484 */
12485function inStr(str, find) {
12486 return str.indexOf(find) > -1;
12487}
12488
12489/**
12490 * split string on whitespace
12491 * @param {String} str
12492 * @returns {Array} words
12493 */
12494function splitStr(str) {
12495 return str.trim().split(/\s+/g);
12496}
12497
12498/**
12499 * find if a array contains the object using indexOf or a simple polyFill
12500 * @param {Array} src
12501 * @param {String} find
12502 * @param {String} [findByKey]
12503 * @return {Boolean|Number} false when not found, or the index
12504 */
12505function inArray(src, find, findByKey) {
12506 if (src.indexOf && !findByKey) {
12507 return src.indexOf(find);
12508 } else {
12509 var i = 0;
12510 while (i < src.length) {
12511 if ((findByKey && src[i][findByKey] == find) || (!findByKey && src[i] === find)) {
12512 return i;
12513 }
12514 i++;
12515 }
12516 return -1;
12517 }
12518}
12519
12520/**
12521 * convert array-like objects to real arrays
12522 * @param {Object} obj
12523 * @returns {Array}
12524 */
12525function toArray(obj) {
12526 return Array.prototype.slice.call(obj, 0);
12527}
12528
12529/**
12530 * unique array with objects based on a key (like 'id') or just by the array's value
12531 * @param {Array} src [{id:1},{id:2},{id:1}]
12532 * @param {String} [key]
12533 * @param {Boolean} [sort=False]
12534 * @returns {Array} [{id:1},{id:2}]
12535 */
12536function uniqueArray(src, key, sort) {
12537 var results = [];
12538 var values = [];
12539 var i = 0;
12540
12541 while (i < src.length) {
12542 var val = key ? src[i][key] : src[i];
12543 if (inArray(values, val) < 0) {
12544 results.push(src[i]);
12545 }
12546 values[i] = val;
12547 i++;
12548 }
12549
12550 if (sort) {
12551 if (!key) {
12552 results = results.sort();
12553 } else {
12554 results = results.sort(function sortUniqueArray(a, b) {
12555 return a[key] > b[key];
12556 });
12557 }
12558 }
12559
12560 return results;
12561}
12562
12563/**
12564 * get the prefixed property
12565 * @param {Object} obj
12566 * @param {String} property
12567 * @returns {String|Undefined} prefixed
12568 */
12569function prefixed(obj, property) {
12570 var prefix, prop;
12571 var camelProp = property[0].toUpperCase() + property.slice(1);
12572
12573 var i = 0;
12574 while (i < VENDOR_PREFIXES.length) {
12575 prefix = VENDOR_PREFIXES[i];
12576 prop = (prefix) ? prefix + camelProp : property;
12577
12578 if (prop in obj) {
12579 return prop;
12580 }
12581 i++;
12582 }
12583 return undefined;
12584}
12585
12586/**
12587 * get a unique id
12588 * @returns {number} uniqueId
12589 */
12590var _uniqueId = 1;
12591function uniqueId() {
12592 return _uniqueId++;
12593}
12594
12595/**
12596 * get the window object of an element
12597 * @param {HTMLElement} element
12598 * @returns {DocumentView|Window}
12599 */
12600function getWindowForElement(element) {
12601 var doc = element.ownerDocument || element;
12602 return (doc.defaultView || doc.parentWindow || window);
12603}
12604
12605var MOBILE_REGEX = /mobile|tablet|ip(ad|hone|od)|android/i;
12606
12607var SUPPORT_TOUCH = ('ontouchstart' in window);
12608// var SUPPORT_POINTER_EVENTS = prefixed(window, 'PointerEvent') !== undefined;
12609var SUPPORT_POINTER_EVENTS = false;
12610var SUPPORT_ONLY_TOUCH = SUPPORT_TOUCH && MOBILE_REGEX.test(navigator.userAgent);
12611
12612var INPUT_TYPE_TOUCH = 'touch';
12613var INPUT_TYPE_PEN = 'pen';
12614var INPUT_TYPE_MOUSE = 'mouse';
12615var INPUT_TYPE_KINECT = 'kinect';
12616
12617var COMPUTE_INTERVAL = 25;
12618
12619var INPUT_START = 1;
12620var INPUT_MOVE = 2;
12621var INPUT_END = 4;
12622var INPUT_CANCEL = 8;
12623
12624var DIRECTION_NONE = 1;
12625var DIRECTION_LEFT = 2;
12626var DIRECTION_RIGHT = 4;
12627var DIRECTION_UP = 8;
12628var DIRECTION_DOWN = 16;
12629
12630var DIRECTION_HORIZONTAL = DIRECTION_LEFT | DIRECTION_RIGHT;
12631var DIRECTION_VERTICAL = DIRECTION_UP | DIRECTION_DOWN;
12632var DIRECTION_ALL = DIRECTION_HORIZONTAL | DIRECTION_VERTICAL;
12633
12634var PROPS_XY = ['x', 'y'];
12635var PROPS_CLIENT_XY = ['clientX', 'clientY'];
12636
12637/**
12638 * create new input type manager
12639 * @param {Manager} manager
12640 * @param {Function} callback
12641 * @returns {Input}
12642 * @constructor
12643 */
12644function Input(manager, callback) {
12645 var self = this;
12646 this.manager = manager;
12647 this.callback = callback;
12648 this.element = manager.element;
12649 this.target = manager.options.inputTarget;
12650
12651 // smaller wrapper around the handler, for the scope and the enabled state of the manager,
12652 // so when disabled the input events are completely bypassed.
12653 this.domHandler = function(ev) {
12654 if (boolOrFn(manager.options.enable, [manager])) {
12655 self.handler(ev);
12656 }
12657 };
12658
12659 this.init();
12660
12661}
12662
12663Input.prototype = {
12664 /**
12665 * should handle the inputEvent data and trigger the callback
12666 * @virtual
12667 */
12668 handler: function() { },
12669
12670 /**
12671 * bind the events
12672 */
12673 init: function() {
12674 this.evEl && addEventListeners(this.element, this.evEl, this.domHandler);
12675 this.evTarget && addEventListeners(this.target, this.evTarget, this.domHandler);
12676 this.evWin && addEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler);
12677 },
12678
12679 /**
12680 * unbind the events
12681 */
12682 destroy: function() {
12683 this.evEl && removeEventListeners(this.element, this.evEl, this.domHandler);
12684 this.evTarget && removeEventListeners(this.target, this.evTarget, this.domHandler);
12685 this.evWin && removeEventListeners(getWindowForElement(this.element), this.evWin, this.domHandler);
12686 }
12687};
12688
12689/**
12690 * create new input type manager
12691 * called by the Manager constructor
12692 * @param {NGHammer} manager
12693 * @returns {Input}
12694 */
12695function createInputInstance(manager) {
12696 var Type;
12697 var inputClass = manager.options.inputClass;
12698
12699 if (inputClass) {
12700 Type = inputClass;
12701 } else if (SUPPORT_POINTER_EVENTS) {
12702 Type = PointerEventInput;
12703 } else if (SUPPORT_ONLY_TOUCH) {
12704 Type = TouchInput;
12705 } else if (!SUPPORT_TOUCH) {
12706 Type = MouseInput;
12707 } else {
12708 Type = TouchMouseInput;
12709 }
12710 return new (Type)(manager, inputHandler);
12711}
12712
12713/**
12714 * handle input events
12715 * @param {Manager} manager
12716 * @param {String} eventType
12717 * @param {Object} input
12718 */
12719function inputHandler(manager, eventType, input) {
12720 var pointersLen = input.pointers.length;
12721 var changedPointersLen = input.changedPointers.length;
12722 var isFirst = (eventType & INPUT_START && (pointersLen - changedPointersLen === 0));
12723 var isFinal = (eventType & (INPUT_END | INPUT_CANCEL) && (pointersLen - changedPointersLen === 0));
12724
12725 input.isFirst = !!isFirst;
12726 input.isFinal = !!isFinal;
12727
12728 if (isFirst) {
12729 manager.session = {};
12730 }
12731
12732 // source event is the normalized value of the domEvents
12733 // like 'touchstart, mouseup, pointerdown'
12734 input.eventType = eventType;
12735
12736 // compute scale, rotation etc
12737 computeInputData(manager, input);
12738
12739 // emit secret event
12740 manager.emit('hammer.input', input);
12741
12742 manager.recognize(input);
12743 manager.session.prevInput = input;
12744}
12745
12746/**
12747 * extend the data with some usable properties like scale, rotate, velocity etc
12748 * @param {Object} manager
12749 * @param {Object} input
12750 */
12751function computeInputData(manager, input) {
12752 var session = manager.session;
12753 var pointers = input.pointers;
12754 var pointersLength = pointers.length;
12755
12756 // store the first input to calculate the distance and direction
12757 if (!session.firstInput) {
12758 session.firstInput = simpleCloneInputData(input);
12759 }
12760
12761 // to compute scale and rotation we need to store the multiple touches
12762 if (pointersLength > 1 && !session.firstMultiple) {
12763 session.firstMultiple = simpleCloneInputData(input);
12764 } else if (pointersLength === 1) {
12765 session.firstMultiple = false;
12766 }
12767
12768 var firstInput = session.firstInput;
12769 var firstMultiple = session.firstMultiple;
12770 var offsetCenter = firstMultiple ? firstMultiple.center : firstInput.center;
12771
12772 var center = input.center = getCenter(pointers);
12773 input.timeStamp = now();
12774 input.deltaTime = input.timeStamp - firstInput.timeStamp;
12775
12776 input.angle = getAngle(offsetCenter, center);
12777 input.distance = getDistance(offsetCenter, center);
12778
12779 computeDeltaXY(session, input);
12780 input.offsetDirection = getDirection(input.deltaX, input.deltaY);
12781
12782 var overallVelocity = getVelocity(input.deltaTime, input.deltaX, input.deltaY);
12783 input.overallVelocityX = overallVelocity.x;
12784 input.overallVelocityY = overallVelocity.y;
12785 input.overallVelocity = (abs(overallVelocity.x) > abs(overallVelocity.y)) ? overallVelocity.x : overallVelocity.y;
12786
12787 input.scale = firstMultiple ? getScale(firstMultiple.pointers, pointers) : 1;
12788 input.rotation = firstMultiple ? getRotation(firstMultiple.pointers, pointers) : 0;
12789
12790 input.maxPointers = !session.prevInput ? input.pointers.length : ((input.pointers.length >
12791 session.prevInput.maxPointers) ? input.pointers.length : session.prevInput.maxPointers);
12792
12793 computeIntervalInputData(session, input);
12794
12795 // find the correct target
12796 var target = manager.element;
12797 if (hasParent(input.srcEvent.target, target)) {
12798 target = input.srcEvent.target;
12799 }
12800 input.target = target;
12801}
12802
12803function computeDeltaXY(session, input) {
12804 var center = input.center;
12805 var offset = session.offsetDelta || {};
12806 var prevDelta = session.prevDelta || {};
12807 var prevInput = session.prevInput || {};
12808
12809 if (input.eventType === INPUT_START || prevInput.eventType === INPUT_END) {
12810 prevDelta = session.prevDelta = {
12811 x: prevInput.deltaX || 0,
12812 y: prevInput.deltaY || 0
12813 };
12814
12815 offset = session.offsetDelta = {
12816 x: center.x,
12817 y: center.y
12818 };
12819 }
12820
12821 input.deltaX = prevDelta.x + (center.x - offset.x);
12822 input.deltaY = prevDelta.y + (center.y - offset.y);
12823}
12824
12825/**
12826 * velocity is calculated every x ms
12827 * @param {Object} session
12828 * @param {Object} input
12829 */
12830function computeIntervalInputData(session, input) {
12831 var last = session.lastInterval || input,
12832 deltaTime = input.timeStamp - last.timeStamp,
12833 velocity, velocityX, velocityY, direction;
12834
12835 if (input.eventType != INPUT_CANCEL && (deltaTime > COMPUTE_INTERVAL || last.velocity === undefined)) {
12836 var deltaX = input.deltaX - last.deltaX;
12837 var deltaY = input.deltaY - last.deltaY;
12838
12839 var v = getVelocity(deltaTime, deltaX, deltaY);
12840 velocityX = v.x;
12841 velocityY = v.y;
12842 velocity = (abs(v.x) > abs(v.y)) ? v.x : v.y;
12843 direction = getDirection(deltaX, deltaY);
12844
12845 session.lastInterval = input;
12846 } else {
12847 // use latest velocity info if it doesn't overtake a minimum period
12848 velocity = last.velocity;
12849 velocityX = last.velocityX;
12850 velocityY = last.velocityY;
12851 direction = last.direction;
12852 }
12853
12854 input.velocity = velocity;
12855 input.velocityX = velocityX;
12856 input.velocityY = velocityY;
12857 input.direction = direction;
12858}
12859
12860/**
12861 * create a simple clone from the input used for storage of firstInput and firstMultiple
12862 * @param {Object} input
12863 * @returns {Object} clonedInputData
12864 */
12865function simpleCloneInputData(input) {
12866 // make a simple copy of the pointers because we will get a reference if we don't
12867 // we only need clientXY for the calculations
12868 var pointers = [];
12869 var i = 0;
12870 while (i < input.pointers.length) {
12871 pointers[i] = {
12872 clientX: round(input.pointers[i].clientX),
12873 clientY: round(input.pointers[i].clientY)
12874 };
12875 i++;
12876 }
12877
12878 return {
12879 timeStamp: now(),
12880 pointers: pointers,
12881 center: getCenter(pointers),
12882 deltaX: input.deltaX,
12883 deltaY: input.deltaY
12884 };
12885}
12886
12887/**
12888 * get the center of all the pointers
12889 * @param {Array} pointers
12890 * @return {Object} center contains `x` and `y` properties
12891 */
12892function getCenter(pointers) {
12893 var pointersLength = pointers.length;
12894
12895 // no need to loop when only one touch
12896 if (pointersLength === 1) {
12897 return {
12898 x: round(pointers[0].clientX),
12899 y: round(pointers[0].clientY)
12900 };
12901 }
12902
12903 var x = 0, y = 0, i = 0;
12904 while (i < pointersLength) {
12905 x += pointers[i].clientX;
12906 y += pointers[i].clientY;
12907 i++;
12908 }
12909
12910 return {
12911 x: round(x / pointersLength),
12912 y: round(y / pointersLength)
12913 };
12914}
12915
12916/**
12917 * calculate the velocity between two points. unit is in px per ms.
12918 * @param {Number} deltaTime
12919 * @param {Number} x
12920 * @param {Number} y
12921 * @return {Object} velocity `x` and `y`
12922 */
12923function getVelocity(deltaTime, x, y) {
12924 return {
12925 x: x / deltaTime || 0,
12926 y: y / deltaTime || 0
12927 };
12928}
12929
12930/**
12931 * get the direction between two points
12932 * @param {Number} x
12933 * @param {Number} y
12934 * @return {Number} direction
12935 */
12936function getDirection(x, y) {
12937 if (x === y) {
12938 return DIRECTION_NONE;
12939 }
12940
12941 if (abs(x) >= abs(y)) {
12942 return x < 0 ? DIRECTION_LEFT : DIRECTION_RIGHT;
12943 }
12944 return y < 0 ? DIRECTION_UP : DIRECTION_DOWN;
12945}
12946
12947/**
12948 * calculate the absolute distance between two points
12949 * @param {Object} p1 {x, y}
12950 * @param {Object} p2 {x, y}
12951 * @param {Array} [props] containing x and y keys
12952 * @return {Number} distance
12953 */
12954function getDistance(p1, p2, props) {
12955 if (!props) {
12956 props = PROPS_XY;
12957 }
12958 var x = p2[props[0]] - p1[props[0]],
12959 y = p2[props[1]] - p1[props[1]];
12960
12961 return Math.sqrt((x * x) + (y * y));
12962}
12963
12964/**
12965 * calculate the angle between two coordinates
12966 * @param {Object} p1
12967 * @param {Object} p2
12968 * @param {Array} [props] containing x and y keys
12969 * @return {Number} angle
12970 */
12971function getAngle(p1, p2, props) {
12972 if (!props) {
12973 props = PROPS_XY;
12974 }
12975 var x = p2[props[0]] - p1[props[0]],
12976 y = p2[props[1]] - p1[props[1]];
12977 return Math.atan2(y, x) * 180 / Math.PI;
12978}
12979
12980/**
12981 * calculate the rotation degrees between two pointersets
12982 * @param {Array} start array of pointers
12983 * @param {Array} end array of pointers
12984 * @return {Number} rotation
12985 */
12986function getRotation(start, end) {
12987 return getAngle(end[1], end[0], PROPS_CLIENT_XY) + getAngle(start[1], start[0], PROPS_CLIENT_XY);
12988}
12989
12990/**
12991 * calculate the scale factor between two pointersets
12992 * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out
12993 * @param {Array} start array of pointers
12994 * @param {Array} end array of pointers
12995 * @return {Number} scale
12996 */
12997function getScale(start, end) {
12998 return getDistance(end[0], end[1], PROPS_CLIENT_XY) / getDistance(start[0], start[1], PROPS_CLIENT_XY);
12999}
13000
13001var MOUSE_INPUT_MAP = {
13002 mousedown: INPUT_START,
13003 mousemove: INPUT_MOVE,
13004 mouseup: INPUT_END
13005};
13006
13007var MOUSE_ELEMENT_EVENTS = 'mousedown';
13008var MOUSE_WINDOW_EVENTS = 'mousemove mouseup';
13009
13010/**
13011 * Mouse events input
13012 * @constructor
13013 * @extends Input
13014 */
13015function MouseInput() {
13016 this.evEl = MOUSE_ELEMENT_EVENTS;
13017 this.evWin = MOUSE_WINDOW_EVENTS;
13018
13019 this.pressed = false; // mousedown state
13020
13021 Input.apply(this, arguments);
13022}
13023
13024inherit(MouseInput, Input, {
13025 /**
13026 * handle mouse events
13027 * @param {Object} ev
13028 */
13029 handler: function MEhandler(ev) {
13030 var eventType = MOUSE_INPUT_MAP[ev.type];
13031
13032 // on start we want to have the left mouse button down
13033 if (eventType & INPUT_START && ev.button === 0) {
13034 this.pressed = true;
13035 }
13036
13037 if (eventType & INPUT_MOVE && ev.which !== 1) {
13038 eventType = INPUT_END;
13039 }
13040
13041 // mouse must be down
13042 if (!this.pressed) {
13043 return;
13044 }
13045
13046 if (eventType & INPUT_END) {
13047 this.pressed = false;
13048 }
13049
13050 this.callback(this.manager, eventType, {
13051 pointers: [ev],
13052 changedPointers: [ev],
13053 pointerType: INPUT_TYPE_MOUSE,
13054 srcEvent: ev
13055 });
13056 }
13057});
13058
13059var POINTER_INPUT_MAP = {
13060 pointerdown: INPUT_START,
13061 pointermove: INPUT_MOVE,
13062 pointerup: INPUT_END,
13063 pointercancel: INPUT_CANCEL,
13064 pointerout: INPUT_CANCEL
13065};
13066
13067// in IE10 the pointer types is defined as an enum
13068var IE10_POINTER_TYPE_ENUM = {
13069 2: INPUT_TYPE_TOUCH,
13070 3: INPUT_TYPE_PEN,
13071 4: INPUT_TYPE_MOUSE,
13072 5: INPUT_TYPE_KINECT // see https://twitter.com/jacobrossi/status/480596438489890816
13073};
13074
13075var POINTER_ELEMENT_EVENTS = 'pointerdown';
13076var POINTER_WINDOW_EVENTS = 'pointermove pointerup pointercancel';
13077
13078// IE10 has prefixed support, and case-sensitive
13079if (window.MSPointerEvent && !window.PointerEvent) {
13080 POINTER_ELEMENT_EVENTS = 'MSPointerDown';
13081 POINTER_WINDOW_EVENTS = 'MSPointerMove MSPointerUp MSPointerCancel';
13082}
13083
13084/**
13085 * Pointer events input
13086 * @constructor
13087 * @extends Input
13088 */
13089function PointerEventInput() {
13090 this.evEl = POINTER_ELEMENT_EVENTS;
13091 this.evWin = POINTER_WINDOW_EVENTS;
13092
13093 Input.apply(this, arguments);
13094
13095 this.store = (this.manager.session.pointerEvents = []);
13096}
13097
13098inherit(PointerEventInput, Input, {
13099 /**
13100 * handle mouse events
13101 * @param {Object} ev
13102 */
13103 handler: function PEhandler(ev) {
13104 var store = this.store;
13105 var removePointer = false;
13106
13107 var eventTypeNormalized = ev.type.toLowerCase().replace('ms', '');
13108 var eventType = POINTER_INPUT_MAP[eventTypeNormalized];
13109 var pointerType = IE10_POINTER_TYPE_ENUM[ev.pointerType] || ev.pointerType;
13110
13111 var isTouch = (pointerType == INPUT_TYPE_TOUCH);
13112
13113 // get index of the event in the store
13114 var storeIndex = inArray(store, ev.pointerId, 'pointerId');
13115
13116 // start and mouse must be down
13117 if (eventType & INPUT_START && (ev.button === 0 || isTouch)) {
13118 if (storeIndex < 0) {
13119 store.push(ev);
13120 storeIndex = store.length - 1;
13121 }
13122 } else if (eventType & (INPUT_END | INPUT_CANCEL)) {
13123 removePointer = true;
13124 }
13125
13126 // it not found, so the pointer hasn't been down (so it's probably a hover)
13127 if (storeIndex < 0) {
13128 return;
13129 }
13130
13131 // update the event in the store
13132 store[storeIndex] = ev;
13133
13134 this.callback(this.manager, eventType, {
13135 pointers: store,
13136 changedPointers: [ev],
13137 pointerType: pointerType,
13138 srcEvent: ev
13139 });
13140
13141 if (removePointer) {
13142 // remove from the store
13143 store.splice(storeIndex, 1);
13144 }
13145 }
13146});
13147
13148var SINGLE_TOUCH_INPUT_MAP = {
13149 touchstart: INPUT_START,
13150 touchmove: INPUT_MOVE,
13151 touchend: INPUT_END,
13152 touchcancel: INPUT_CANCEL
13153};
13154
13155var SINGLE_TOUCH_TARGET_EVENTS = 'touchstart';
13156var SINGLE_TOUCH_WINDOW_EVENTS = 'touchstart touchmove touchend touchcancel';
13157
13158/**
13159 * Touch events input
13160 * @constructor
13161 * @extends Input
13162 */
13163function SingleTouchInput() {
13164 this.evTarget = SINGLE_TOUCH_TARGET_EVENTS;
13165 this.evWin = SINGLE_TOUCH_WINDOW_EVENTS;
13166 this.started = false;
13167
13168 Input.apply(this, arguments);
13169}
13170
13171inherit(SingleTouchInput, Input, {
13172 handler: function TEhandler(ev) {
13173 var type = SINGLE_TOUCH_INPUT_MAP[ev.type];
13174
13175 // should we handle the touch events?
13176 if (type === INPUT_START) {
13177 this.started = true;
13178 }
13179
13180 if (!this.started) {
13181 return;
13182 }
13183
13184 var touches = normalizeSingleTouches.call(this, ev, type);
13185
13186 // when done, reset the started state
13187 if (type & (INPUT_END | INPUT_CANCEL) && touches[0].length - touches[1].length === 0) {
13188 this.started = false;
13189 }
13190
13191 this.callback(this.manager, type, {
13192 pointers: touches[0],
13193 changedPointers: touches[1],
13194 pointerType: INPUT_TYPE_TOUCH,
13195 srcEvent: ev
13196 });
13197 }
13198});
13199
13200/**
13201 * @this {TouchInput}
13202 * @param {Object} ev
13203 * @param {Number} type flag
13204 * @returns {undefined|Array} [all, changed]
13205 */
13206function normalizeSingleTouches(ev, type) {
13207 var all = toArray(ev.touches);
13208 var changed = toArray(ev.changedTouches);
13209
13210 if (type & (INPUT_END | INPUT_CANCEL)) {
13211 all = uniqueArray(all.concat(changed), 'identifier', true);
13212 }
13213
13214 return [all, changed];
13215}
13216
13217var TOUCH_INPUT_MAP = {
13218 touchstart: INPUT_START,
13219 touchmove: INPUT_MOVE,
13220 touchend: INPUT_END,
13221 touchcancel: INPUT_CANCEL
13222};
13223
13224var TOUCH_TARGET_EVENTS = 'touchstart touchmove touchend touchcancel';
13225
13226/**
13227 * Multi-user touch events input
13228 * @constructor
13229 * @extends Input
13230 */
13231function TouchInput() {
13232 this.evTarget = TOUCH_TARGET_EVENTS;
13233 this.targetIds = {};
13234
13235 Input.apply(this, arguments);
13236}
13237
13238inherit(TouchInput, Input, {
13239 handler: function MTEhandler(ev) {
13240 var type = TOUCH_INPUT_MAP[ev.type];
13241 var touches = getTouches.call(this, ev, type);
13242 if (!touches) {
13243 return;
13244 }
13245
13246 this.callback(this.manager, type, {
13247 pointers: touches[0],
13248 changedPointers: touches[1],
13249 pointerType: INPUT_TYPE_TOUCH,
13250 srcEvent: ev
13251 });
13252 }
13253});
13254
13255/**
13256 * @this {TouchInput}
13257 * @param {Object} ev
13258 * @param {Number} type flag
13259 * @returns {undefined|Array} [all, changed]
13260 */
13261function getTouches(ev, type) {
13262 var allTouches = toArray(ev.touches);
13263 var targetIds = this.targetIds;
13264
13265 // when there is only one touch, the process can be simplified
13266 if (type & (INPUT_START | INPUT_MOVE) && allTouches.length === 1) {
13267 targetIds[allTouches[0].identifier] = true;
13268 return [allTouches, allTouches];
13269 }
13270
13271 var i,
13272 targetTouches,
13273 changedTouches = toArray(ev.changedTouches),
13274 changedTargetTouches = [],
13275 target = this.target;
13276
13277 // get target touches from touches
13278 targetTouches = allTouches.filter(function(touch) {
13279 return hasParent(touch.target, target);
13280 });
13281
13282 // collect touches
13283 if (type === INPUT_START) {
13284 i = 0;
13285 while (i < targetTouches.length) {
13286 targetIds[targetTouches[i].identifier] = true;
13287 i++;
13288 }
13289 }
13290
13291 // filter changed touches to only contain touches that exist in the collected target ids
13292 i = 0;
13293 while (i < changedTouches.length) {
13294 if (targetIds[changedTouches[i].identifier]) {
13295 changedTargetTouches.push(changedTouches[i]);
13296 }
13297
13298 // cleanup removed touches
13299 if (type & (INPUT_END | INPUT_CANCEL)) {
13300 delete targetIds[changedTouches[i].identifier];
13301 }
13302 i++;
13303 }
13304
13305 if (!changedTargetTouches.length) {
13306 return;
13307 }
13308
13309 return [
13310 // merge targetTouches with changedTargetTouches so it contains ALL touches, including 'end' and 'cancel'
13311 uniqueArray(targetTouches.concat(changedTargetTouches), 'identifier', true),
13312 changedTargetTouches
13313 ];
13314}
13315
13316/**
13317 * Combined touch and mouse input
13318 *
13319 * Touch has a higher priority then mouse, and while touching no mouse events are allowed.
13320 * This because touch devices also emit mouse events while doing a touch.
13321 *
13322 * @constructor
13323 * @extends Input
13324 */
13325
13326var DEDUP_TIMEOUT = 2500;
13327var DEDUP_DISTANCE = 25;
13328
13329function TouchMouseInput() {
13330 Input.apply(this, arguments);
13331
13332 var handler = bindFn(this.handler, this);
13333 this.touch = new TouchInput(this.manager, handler);
13334 this.mouse = new MouseInput(this.manager, handler);
13335
13336 this.primaryTouch = null;
13337 this.lastTouches = [];
13338}
13339
13340inherit(TouchMouseInput, Input, {
13341 /**
13342 * handle mouse and touch events
13343 * @param {NGHammer} manager
13344 * @param {String} inputEvent
13345 * @param {Object} inputData
13346 */
13347 handler: function TMEhandler(manager, inputEvent, inputData) {
13348 var isTouch = (inputData.pointerType == INPUT_TYPE_TOUCH),
13349 isMouse = (inputData.pointerType == INPUT_TYPE_MOUSE);
13350
13351 if (isMouse && inputData.sourceCapabilities && inputData.sourceCapabilities.firesTouchEvents) {
13352 return;
13353 }
13354
13355 // when we're in a touch event, record touches to de-dupe synthetic mouse event
13356 if (isTouch) {
13357 recordTouches.call(this, inputEvent, inputData);
13358 } else if (isMouse && isSyntheticEvent.call(this, inputData)) {
13359 return;
13360 }
13361
13362 this.callback(manager, inputEvent, inputData);
13363 },
13364
13365 /**
13366 * remove the event listeners
13367 */
13368 destroy: function destroy() {
13369 this.touch.destroy();
13370 this.mouse.destroy();
13371 }
13372});
13373
13374function recordTouches(eventType, eventData) {
13375 if (eventType & INPUT_START) {
13376 this.primaryTouch = eventData.changedPointers[0].identifier;
13377 setLastTouch.call(this, eventData);
13378 } else if (eventType & (INPUT_END | INPUT_CANCEL)) {
13379 setLastTouch.call(this, eventData);
13380 }
13381}
13382
13383function setLastTouch(eventData) {
13384 var touch = eventData.changedPointers[0];
13385
13386 if (touch.identifier === this.primaryTouch) {
13387 var lastTouch = {x: touch.clientX, y: touch.clientY};
13388 this.lastTouches.push(lastTouch);
13389 var lts = this.lastTouches;
13390 var removeLastTouch = function() {
13391 var i = lts.indexOf(lastTouch);
13392 if (i > -1) {
13393 lts.splice(i, 1);
13394 }
13395 };
13396 setTimeout(removeLastTouch, DEDUP_TIMEOUT);
13397 }
13398}
13399
13400function isSyntheticEvent(eventData) {
13401 var x = eventData.srcEvent.clientX, y = eventData.srcEvent.clientY;
13402 for (var i = 0; i < this.lastTouches.length; i++) {
13403 var t = this.lastTouches[i];
13404 var dx = Math.abs(x - t.x), dy = Math.abs(y - t.y);
13405 if (dx <= DEDUP_DISTANCE && dy <= DEDUP_DISTANCE) {
13406 return true;
13407 }
13408 }
13409 return false;
13410}
13411
13412var PREFIXED_TOUCH_ACTION = prefixed(TEST_ELEMENT.style, 'touchAction');
13413var NATIVE_TOUCH_ACTION = PREFIXED_TOUCH_ACTION !== undefined;
13414
13415// magical touchAction value
13416var TOUCH_ACTION_COMPUTE = 'compute';
13417var TOUCH_ACTION_AUTO = 'auto';
13418var TOUCH_ACTION_MANIPULATION = 'manipulation'; // not implemented
13419var TOUCH_ACTION_NONE = 'none';
13420var TOUCH_ACTION_PAN_X = 'pan-x';
13421var TOUCH_ACTION_PAN_Y = 'pan-y';
13422var TOUCH_ACTION_MAP = getTouchActionProps();
13423
13424/**
13425 * Touch Action
13426 * sets the touchAction property or uses the js alternative
13427 * @param {Manager} manager
13428 * @param {String} value
13429 * @constructor
13430 */
13431function TouchAction(manager, value) {
13432 this.manager = manager;
13433 this.set(value);
13434}
13435
13436TouchAction.prototype = {
13437 /**
13438 * set the touchAction value on the element or enable the polyfill
13439 * @param {String} value
13440 */
13441 set: function(value) {
13442 // find out the touch-action by the event handlers
13443 if (value == TOUCH_ACTION_COMPUTE) {
13444 value = this.compute();
13445 }
13446
13447 if (NATIVE_TOUCH_ACTION && this.manager.element.style && TOUCH_ACTION_MAP[value]) {
13448 this.manager.element.style[PREFIXED_TOUCH_ACTION] = value;
13449 }
13450 this.actions = value.toLowerCase().trim();
13451 },
13452
13453 /**
13454 * just re-set the touchAction value
13455 */
13456 update: function() {
13457 this.set(this.manager.options.touchAction);
13458 },
13459
13460 /**
13461 * compute the value for the touchAction property based on the recognizer's settings
13462 * @returns {String} value
13463 */
13464 compute: function() {
13465 var actions = [];
13466 each(this.manager.recognizers, function(recognizer) {
13467 if (boolOrFn(recognizer.options.enable, [recognizer])) {
13468 actions = actions.concat(recognizer.getTouchAction());
13469 }
13470 });
13471 return cleanTouchActions(actions.join(' '));
13472 },
13473
13474 /**
13475 * this method is called on each input cycle and provides the preventing of the browser behavior
13476 * @param {Object} input
13477 */
13478 preventDefaults: function(input) {
13479 var srcEvent = input.srcEvent;
13480 var direction = input.offsetDirection;
13481
13482 // if the touch action did prevented once this session
13483 if (this.manager.session.prevented) {
13484 srcEvent.preventDefault();
13485 return;
13486 }
13487
13488 var actions = this.actions;
13489 var hasNone = inStr(actions, TOUCH_ACTION_NONE) && !TOUCH_ACTION_MAP[TOUCH_ACTION_NONE];
13490 var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_Y];
13491 var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X) && !TOUCH_ACTION_MAP[TOUCH_ACTION_PAN_X];
13492
13493 if (hasNone) {
13494 //do not prevent defaults if this is a tap gesture
13495
13496 var isTapPointer = input.pointers.length === 1;
13497 var isTapMovement = input.distance < 2;
13498 var isTapTouchTime = input.deltaTime < 250;
13499
13500 if (isTapPointer && isTapMovement && isTapTouchTime) {
13501 return;
13502 }
13503 }
13504
13505 if (hasPanX && hasPanY) {
13506 // `pan-x pan-y` means browser handles all scrolling/panning, do not prevent
13507 return;
13508 }
13509
13510 if (hasNone ||
13511 (hasPanY && direction & DIRECTION_HORIZONTAL) ||
13512 (hasPanX && direction & DIRECTION_VERTICAL)) {
13513 return this.preventSrc(srcEvent);
13514 }
13515 },
13516
13517 /**
13518 * call preventDefault to prevent the browser's default behavior (scrolling in most cases)
13519 * @param {Object} srcEvent
13520 */
13521 preventSrc: function(srcEvent) {
13522 this.manager.session.prevented = true;
13523 srcEvent.preventDefault();
13524 }
13525};
13526
13527/**
13528 * when the touchActions are collected they are not a valid value, so we need to clean things up. *
13529 * @param {String} actions
13530 * @returns {*}
13531 */
13532function cleanTouchActions(actions) {
13533 // none
13534 if (inStr(actions, TOUCH_ACTION_NONE)) {
13535 return TOUCH_ACTION_NONE;
13536 }
13537
13538 var hasPanX = inStr(actions, TOUCH_ACTION_PAN_X);
13539 var hasPanY = inStr(actions, TOUCH_ACTION_PAN_Y);
13540
13541 // if both pan-x and pan-y are set (different recognizers
13542 // for different directions, e.g. horizontal pan but vertical swipe?)
13543 // we need none (as otherwise with pan-x pan-y combined none of these
13544 // recognizers will work, since the browser would handle all panning
13545 if (hasPanX && hasPanY) {
13546 return TOUCH_ACTION_NONE;
13547 }
13548
13549 // pan-x OR pan-y
13550 if (hasPanX || hasPanY) {
13551 return hasPanX ? TOUCH_ACTION_PAN_X : TOUCH_ACTION_PAN_Y;
13552 }
13553
13554 // manipulation
13555 if (inStr(actions, TOUCH_ACTION_MANIPULATION)) {
13556 return TOUCH_ACTION_MANIPULATION;
13557 }
13558
13559 return TOUCH_ACTION_AUTO;
13560}
13561
13562function getTouchActionProps() {
13563 if (!NATIVE_TOUCH_ACTION) {
13564 return false;
13565 }
13566 var touchMap = {};
13567 var cssSupports = window.CSS && window.CSS.supports;
13568 ['auto', 'manipulation', 'pan-y', 'pan-x', 'pan-x pan-y', 'none'].forEach(function(val) {
13569
13570 // If css.supports is not supported but there is native touch-action assume it supports
13571 // all values. This is the case for IE 10 and 11.
13572 touchMap[val] = cssSupports ? window.CSS.supports('touch-action', val) : true;
13573 });
13574 return touchMap;
13575}
13576
13577/**
13578 * Recognizer flow explained; *
13579 * All recognizers have the initial state of POSSIBLE when a input session starts.
13580 * The definition of a input session is from the first input until the last input, with all it's movement in it. *
13581 * Example session for mouse-input: mousedown -> mousemove -> mouseup
13582 *
13583 * On each recognizing cycle (see Manager.recognize) the .recognize() method is executed
13584 * which determines with state it should be.
13585 *
13586 * If the recognizer has the state FAILED, CANCELLED or RECOGNIZED (equals ENDED), it is reset to
13587 * POSSIBLE to give it another change on the next cycle.
13588 *
13589 * Possible
13590 * |
13591 * +-----+---------------+
13592 * | |
13593 * +-----+-----+ |
13594 * | | |
13595 * Failed Cancelled |
13596 * +-------+------+
13597 * | |
13598 * Recognized Began
13599 * |
13600 * Changed
13601 * |
13602 * Ended/Recognized
13603 */
13604var STATE_POSSIBLE = 1;
13605var STATE_BEGAN = 2;
13606var STATE_CHANGED = 4;
13607var STATE_ENDED = 8;
13608var STATE_RECOGNIZED = STATE_ENDED;
13609var STATE_CANCELLED = 16;
13610var STATE_FAILED = 32;
13611
13612/**
13613 * Recognizer
13614 * Every recognizer needs to extend from this class.
13615 * @constructor
13616 * @param {Object} options
13617 */
13618function Recognizer(options) {
13619 this.options = assign({}, this.defaults, options || {});
13620
13621 this.id = uniqueId();
13622
13623 this.manager = null;
13624
13625 // default is enable true
13626 this.options.enable = ifUndefined(this.options.enable, true);
13627
13628 this.state = STATE_POSSIBLE;
13629
13630 this.simultaneous = {};
13631 this.requireFail = [];
13632}
13633
13634Recognizer.prototype = {
13635 /**
13636 * @virtual
13637 * @type {Object}
13638 */
13639 defaults: {},
13640
13641 /**
13642 * set options
13643 * @param {Object} options
13644 * @return {Recognizer}
13645 */
13646 set: function(options) {
13647 assign(this.options, options);
13648
13649 // also update the touchAction, in case something changed about the directions/enabled state
13650 this.manager && this.manager.touchAction.update();
13651 return this;
13652 },
13653
13654 /**
13655 * recognize simultaneous with an other recognizer.
13656 * @param {Recognizer} otherRecognizer
13657 * @returns {Recognizer} this
13658 */
13659 recognizeWith: function(otherRecognizer) {
13660 if (invokeArrayArg(otherRecognizer, 'recognizeWith', this)) {
13661 return this;
13662 }
13663
13664 var simultaneous = this.simultaneous;
13665 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
13666 if (!simultaneous[otherRecognizer.id]) {
13667 simultaneous[otherRecognizer.id] = otherRecognizer;
13668 otherRecognizer.recognizeWith(this);
13669 }
13670 return this;
13671 },
13672
13673 /**
13674 * drop the simultaneous link. it doesnt remove the link on the other recognizer.
13675 * @param {Recognizer} otherRecognizer
13676 * @returns {Recognizer} this
13677 */
13678 dropRecognizeWith: function(otherRecognizer) {
13679 if (invokeArrayArg(otherRecognizer, 'dropRecognizeWith', this)) {
13680 return this;
13681 }
13682
13683 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
13684 delete this.simultaneous[otherRecognizer.id];
13685 return this;
13686 },
13687
13688 /**
13689 * recognizer can only run when an other is failing
13690 * @param {Recognizer} otherRecognizer
13691 * @returns {Recognizer} this
13692 */
13693 requireFailure: function(otherRecognizer) {
13694 if (invokeArrayArg(otherRecognizer, 'requireFailure', this)) {
13695 return this;
13696 }
13697
13698 var requireFail = this.requireFail;
13699 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
13700 if (inArray(requireFail, otherRecognizer) === -1) {
13701 requireFail.push(otherRecognizer);
13702 otherRecognizer.requireFailure(this);
13703 }
13704 return this;
13705 },
13706
13707 /**
13708 * drop the requireFailure link. it does not remove the link on the other recognizer.
13709 * @param {Recognizer} otherRecognizer
13710 * @returns {Recognizer} this
13711 */
13712 dropRequireFailure: function(otherRecognizer) {
13713 if (invokeArrayArg(otherRecognizer, 'dropRequireFailure', this)) {
13714 return this;
13715 }
13716
13717 otherRecognizer = getRecognizerByNameIfManager(otherRecognizer, this);
13718 var index = inArray(this.requireFail, otherRecognizer);
13719 if (index > -1) {
13720 this.requireFail.splice(index, 1);
13721 }
13722 return this;
13723 },
13724
13725 /**
13726 * has require failures boolean
13727 * @returns {boolean}
13728 */
13729 hasRequireFailures: function() {
13730 return this.requireFail.length > 0;
13731 },
13732
13733 /**
13734 * if the recognizer can recognize simultaneous with an other recognizer
13735 * @param {Recognizer} otherRecognizer
13736 * @returns {Boolean}
13737 */
13738 canRecognizeWith: function(otherRecognizer) {
13739 return !!this.simultaneous[otherRecognizer.id];
13740 },
13741
13742 /**
13743 * You should use `tryEmit` instead of `emit` directly to check
13744 * that all the needed recognizers has failed before emitting.
13745 * @param {Object} input
13746 */
13747 emit: function(input) {
13748 var self = this;
13749 var state = this.state;
13750
13751 function emit(event) {
13752 self.manager.emit(event, input);
13753 }
13754
13755 // 'panstart' and 'panmove'
13756 if (state < STATE_ENDED) {
13757 emit(self.options.event + stateStr(state));
13758 }
13759
13760 emit(self.options.event); // simple 'eventName' events
13761
13762 if (input.additionalEvent) { // additional event(panleft, panright, pinchin, pinchout...)
13763 emit(input.additionalEvent);
13764 }
13765
13766 // panend and pancancel
13767 if (state >= STATE_ENDED) {
13768 emit(self.options.event + stateStr(state));
13769 }
13770 },
13771
13772 /**
13773 * Check that all the require failure recognizers has failed,
13774 * if true, it emits a gesture event,
13775 * otherwise, setup the state to FAILED.
13776 * @param {Object} input
13777 */
13778 tryEmit: function(input) {
13779 if (this.canEmit()) {
13780 return this.emit(input);
13781 }
13782 // it's failing anyway
13783 this.state = STATE_FAILED;
13784 },
13785
13786 /**
13787 * can we emit?
13788 * @returns {boolean}
13789 */
13790 canEmit: function() {
13791 var i = 0;
13792 while (i < this.requireFail.length) {
13793 if (!(this.requireFail[i].state & (STATE_FAILED | STATE_POSSIBLE))) {
13794 return false;
13795 }
13796 i++;
13797 }
13798 return true;
13799 },
13800
13801 /**
13802 * update the recognizer
13803 * @param {Object} inputData
13804 */
13805 recognize: function(inputData) {
13806 // make a new copy of the inputData
13807 // so we can change the inputData without messing up the other recognizers
13808 var inputDataClone = assign({}, inputData);
13809
13810 // is is enabled and allow recognizing?
13811 if (!boolOrFn(this.options.enable, [this, inputDataClone])) {
13812 this.reset();
13813 this.state = STATE_FAILED;
13814 return;
13815 }
13816
13817 // reset when we've reached the end
13818 if (this.state & (STATE_RECOGNIZED | STATE_CANCELLED | STATE_FAILED)) {
13819 this.state = STATE_POSSIBLE;
13820 }
13821
13822 this.state = this.process(inputDataClone);
13823
13824 // the recognizer has recognized a gesture
13825 // so trigger an event
13826 if (this.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED | STATE_CANCELLED)) {
13827 this.tryEmit(inputDataClone);
13828 }
13829 },
13830
13831 /**
13832 * return the state of the recognizer
13833 * the actual recognizing happens in this method
13834 * @virtual
13835 * @param {Object} inputData
13836 * @returns {Const} STATE
13837 */
13838 process: function(inputData) { }, // jshint ignore:line
13839
13840 /**
13841 * return the preferred touch-action
13842 * @virtual
13843 * @returns {Array}
13844 */
13845 getTouchAction: function() { },
13846
13847 /**
13848 * called when the gesture isn't allowed to recognize
13849 * like when another is being recognized or it is disabled
13850 * @virtual
13851 */
13852 reset: function() { }
13853};
13854
13855/**
13856 * get a usable string, used as event postfix
13857 * @param {Const} state
13858 * @returns {String} state
13859 */
13860function stateStr(state) {
13861 if (state & STATE_CANCELLED) {
13862 return 'cancel';
13863 } else if (state & STATE_ENDED) {
13864 return 'end';
13865 } else if (state & STATE_CHANGED) {
13866 return 'move';
13867 } else if (state & STATE_BEGAN) {
13868 return 'start';
13869 }
13870 return '';
13871}
13872
13873/**
13874 * direction cons to string
13875 * @param {Const} direction
13876 * @returns {String}
13877 */
13878function directionStr(direction) {
13879 if (direction == DIRECTION_DOWN) {
13880 return 'down';
13881 } else if (direction == DIRECTION_UP) {
13882 return 'up';
13883 } else if (direction == DIRECTION_LEFT) {
13884 return 'left';
13885 } else if (direction == DIRECTION_RIGHT) {
13886 return 'right';
13887 }
13888 return '';
13889}
13890
13891/**
13892 * get a recognizer by name if it is bound to a manager
13893 * @param {Recognizer|String} otherRecognizer
13894 * @param {Recognizer} recognizer
13895 * @returns {Recognizer}
13896 */
13897function getRecognizerByNameIfManager(otherRecognizer, recognizer) {
13898 var manager = recognizer.manager;
13899 if (manager) {
13900 return manager.get(otherRecognizer);
13901 }
13902 return otherRecognizer;
13903}
13904
13905/**
13906 * This recognizer is just used as a base for the simple attribute recognizers.
13907 * @constructor
13908 * @extends Recognizer
13909 */
13910function AttrRecognizer() {
13911 Recognizer.apply(this, arguments);
13912}
13913
13914inherit(AttrRecognizer, Recognizer, {
13915 /**
13916 * @namespace
13917 * @memberof AttrRecognizer
13918 */
13919 defaults: {
13920 /**
13921 * @type {Number}
13922 * @default 1
13923 */
13924 pointers: 1
13925 },
13926
13927 /**
13928 * Used to check if it the recognizer receives valid input, like input.distance > 10.
13929 * @memberof AttrRecognizer
13930 * @param {Object} input
13931 * @returns {Boolean} recognized
13932 */
13933 attrTest: function(input) {
13934 var optionPointers = this.options.pointers;
13935 return optionPointers === 0 || input.pointers.length === optionPointers;
13936 },
13937
13938 /**
13939 * Process the input and return the state for the recognizer
13940 * @memberof AttrRecognizer
13941 * @param {Object} input
13942 * @returns {*} State
13943 */
13944 process: function(input) {
13945 var state = this.state;
13946 var eventType = input.eventType;
13947
13948 var isRecognized = state & (STATE_BEGAN | STATE_CHANGED);
13949 var isValid = this.attrTest(input);
13950
13951 // on cancel input and we've recognized before, return STATE_CANCELLED
13952 if (isRecognized && (eventType & INPUT_CANCEL || !isValid)) {
13953 return state | STATE_CANCELLED;
13954 } else if (isRecognized || isValid) {
13955 if (eventType & INPUT_END) {
13956 return state | STATE_ENDED;
13957 } else if (!(state & STATE_BEGAN)) {
13958 return STATE_BEGAN;
13959 }
13960 return state | STATE_CHANGED;
13961 }
13962 return STATE_FAILED;
13963 }
13964});
13965
13966/**
13967 * Pan
13968 * Recognized when the pointer is down and moved in the allowed direction.
13969 * @constructor
13970 * @extends AttrRecognizer
13971 */
13972function PanRecognizer() {
13973 AttrRecognizer.apply(this, arguments);
13974
13975 this.pX = null;
13976 this.pY = null;
13977}
13978
13979inherit(PanRecognizer, AttrRecognizer, {
13980 /**
13981 * @namespace
13982 * @memberof PanRecognizer
13983 */
13984 defaults: {
13985 event: 'pan',
13986 threshold: 10,
13987 pointers: 1,
13988 direction: DIRECTION_ALL
13989 },
13990
13991 getTouchAction: function() {
13992 var direction = this.options.direction;
13993 var actions = [];
13994 if (direction & DIRECTION_HORIZONTAL) {
13995 actions.push(TOUCH_ACTION_PAN_Y);
13996 }
13997 if (direction & DIRECTION_VERTICAL) {
13998 actions.push(TOUCH_ACTION_PAN_X);
13999 }
14000 return actions;
14001 },
14002
14003 directionTest: function(input) {
14004 var options = this.options;
14005 var hasMoved = true;
14006 var distance = input.distance;
14007 var direction = input.direction;
14008 var x = input.deltaX;
14009 var y = input.deltaY;
14010
14011 // lock to axis?
14012 if (!(direction & options.direction)) {
14013 if (options.direction & DIRECTION_HORIZONTAL) {
14014 direction = (x === 0) ? DIRECTION_NONE : (x < 0) ? DIRECTION_LEFT : DIRECTION_RIGHT;
14015 hasMoved = x != this.pX;
14016 distance = Math.abs(input.deltaX);
14017 } else {
14018 direction = (y === 0) ? DIRECTION_NONE : (y < 0) ? DIRECTION_UP : DIRECTION_DOWN;
14019 hasMoved = y != this.pY;
14020 distance = Math.abs(input.deltaY);
14021 }
14022 }
14023 input.direction = direction;
14024 return hasMoved && distance > options.threshold && direction & options.direction;
14025 },
14026
14027 attrTest: function(input) {
14028 return AttrRecognizer.prototype.attrTest.call(this, input) &&
14029 (this.state & STATE_BEGAN || (!(this.state & STATE_BEGAN) && this.directionTest(input)));
14030 },
14031
14032 emit: function(input) {
14033
14034 this.pX = input.deltaX;
14035 this.pY = input.deltaY;
14036
14037 var direction = directionStr(input.direction);
14038
14039 if (direction) {
14040 input.additionalEvent = this.options.event + direction;
14041 }
14042 this._super.emit.call(this, input);
14043 }
14044});
14045
14046/**
14047 * Pinch
14048 * Recognized when two or more pointers are moving toward (zoom-in) or away from each other (zoom-out).
14049 * @constructor
14050 * @extends AttrRecognizer
14051 */
14052function PinchRecognizer() {
14053 AttrRecognizer.apply(this, arguments);
14054}
14055
14056inherit(PinchRecognizer, AttrRecognizer, {
14057 /**
14058 * @namespace
14059 * @memberof PinchRecognizer
14060 */
14061 defaults: {
14062 event: 'pinch',
14063 threshold: 0,
14064 pointers: 2
14065 },
14066
14067 getTouchAction: function() {
14068 return [TOUCH_ACTION_NONE];
14069 },
14070
14071 attrTest: function(input) {
14072 return this._super.attrTest.call(this, input) &&
14073 (Math.abs(input.scale - 1) > this.options.threshold || this.state & STATE_BEGAN);
14074 },
14075
14076 emit: function(input) {
14077 if (input.scale !== 1) {
14078 var inOut = input.scale < 1 ? 'in' : 'out';
14079 input.additionalEvent = this.options.event + inOut;
14080 }
14081 this._super.emit.call(this, input);
14082 }
14083});
14084
14085/**
14086 * Press
14087 * Recognized when the pointer is down for x ms without any movement.
14088 * @constructor
14089 * @extends Recognizer
14090 */
14091function PressRecognizer() {
14092 Recognizer.apply(this, arguments);
14093
14094 this._timer = null;
14095 this._input = null;
14096}
14097
14098inherit(PressRecognizer, Recognizer, {
14099 /**
14100 * @namespace
14101 * @memberof PressRecognizer
14102 */
14103 defaults: {
14104 event: 'press',
14105 pointers: 1,
14106 time: 251, // minimal time of the pointer to be pressed
14107 threshold: 9 // a minimal movement is ok, but keep it low
14108 },
14109
14110 getTouchAction: function() {
14111 return [TOUCH_ACTION_AUTO];
14112 },
14113
14114 process: function(input) {
14115 var options = this.options;
14116 var validPointers = input.pointers.length === options.pointers;
14117 var validMovement = input.distance < options.threshold;
14118 var validTime = input.deltaTime > options.time;
14119
14120 this._input = input;
14121
14122 // we only allow little movement
14123 // and we've reached an end event, so a tap is possible
14124 if (!validMovement || !validPointers || (input.eventType & (INPUT_END | INPUT_CANCEL) && !validTime)) {
14125 this.reset();
14126 } else if (input.eventType & INPUT_START) {
14127 this.reset();
14128 this._timer = setTimeoutContext(function() {
14129 this.state = STATE_RECOGNIZED;
14130 this.tryEmit();
14131 }, options.time, this);
14132 } else if (input.eventType & INPUT_END) {
14133 return STATE_RECOGNIZED;
14134 }
14135 return STATE_FAILED;
14136 },
14137
14138 reset: function() {
14139 clearTimeout(this._timer);
14140 },
14141
14142 emit: function(input) {
14143 if (this.state !== STATE_RECOGNIZED) {
14144 return;
14145 }
14146
14147 if (input && (input.eventType & INPUT_END)) {
14148 this.manager.emit(this.options.event + 'up', input);
14149 } else {
14150 this._input.timeStamp = now();
14151 this.manager.emit(this.options.event, this._input);
14152 }
14153 }
14154});
14155
14156/**
14157 * Rotate
14158 * Recognized when two or more pointer are moving in a circular motion.
14159 * @constructor
14160 * @extends AttrRecognizer
14161 */
14162function RotateRecognizer() {
14163 AttrRecognizer.apply(this, arguments);
14164}
14165
14166inherit(RotateRecognizer, AttrRecognizer, {
14167 /**
14168 * @namespace
14169 * @memberof RotateRecognizer
14170 */
14171 defaults: {
14172 event: 'rotate',
14173 threshold: 0,
14174 pointers: 2
14175 },
14176
14177 getTouchAction: function() {
14178 return [TOUCH_ACTION_NONE];
14179 },
14180
14181 attrTest: function(input) {
14182 return this._super.attrTest.call(this, input) &&
14183 (Math.abs(input.rotation) > this.options.threshold || this.state & STATE_BEGAN);
14184 }
14185});
14186
14187/**
14188 * Swipe
14189 * Recognized when the pointer is moving fast (velocity), with enough distance in the allowed direction.
14190 * @constructor
14191 * @extends AttrRecognizer
14192 */
14193function SwipeRecognizer() {
14194 AttrRecognizer.apply(this, arguments);
14195}
14196
14197inherit(SwipeRecognizer, AttrRecognizer, {
14198 /**
14199 * @namespace
14200 * @memberof SwipeRecognizer
14201 */
14202 defaults: {
14203 event: 'swipe',
14204 threshold: 10,
14205 velocity: 0.3,
14206 direction: DIRECTION_HORIZONTAL | DIRECTION_VERTICAL,
14207 pointers: 1
14208 },
14209
14210 getTouchAction: function() {
14211 return PanRecognizer.prototype.getTouchAction.call(this);
14212 },
14213
14214 attrTest: function(input) {
14215 var direction = this.options.direction;
14216 var velocity;
14217
14218 if (direction & (DIRECTION_HORIZONTAL | DIRECTION_VERTICAL)) {
14219 velocity = input.overallVelocity;
14220 } else if (direction & DIRECTION_HORIZONTAL) {
14221 velocity = input.overallVelocityX;
14222 } else if (direction & DIRECTION_VERTICAL) {
14223 velocity = input.overallVelocityY;
14224 }
14225
14226 return this._super.attrTest.call(this, input) &&
14227 direction & input.offsetDirection &&
14228 input.distance > this.options.threshold &&
14229 input.maxPointers == this.options.pointers &&
14230 abs(velocity) > this.options.velocity && input.eventType & INPUT_END;
14231 },
14232
14233 emit: function(input) {
14234 var direction = directionStr(input.offsetDirection);
14235 if (direction) {
14236 this.manager.emit(this.options.event + direction, input);
14237 }
14238
14239 this.manager.emit(this.options.event, input);
14240 }
14241});
14242
14243/**
14244 * A tap is ecognized when the pointer is doing a small tap/click. Multiple taps are recognized if they occur
14245 * between the given interval and position. The delay option can be used to recognize multi-taps without firing
14246 * a single tap.
14247 *
14248 * The eventData from the emitted event contains the property `tapCount`, which contains the amount of
14249 * multi-taps being recognized.
14250 * @constructor
14251 * @extends Recognizer
14252 */
14253function TapRecognizer() {
14254 Recognizer.apply(this, arguments);
14255
14256 // previous time and center,
14257 // used for tap counting
14258 this.pTime = false;
14259 this.pCenter = false;
14260
14261 this._timer = null;
14262 this._input = null;
14263 this.count = 0;
14264}
14265
14266inherit(TapRecognizer, Recognizer, {
14267 /**
14268 * @namespace
14269 * @memberof PinchRecognizer
14270 */
14271 defaults: {
14272 event: 'tap',
14273 pointers: 1,
14274 taps: 1,
14275 interval: 300, // max time between the multi-tap taps
14276 time: 250, // max time of the pointer to be down (like finger on the screen)
14277 threshold: 9, // a minimal movement is ok, but keep it low
14278 posThreshold: 10 // a multi-tap can be a bit off the initial position
14279 },
14280
14281 getTouchAction: function() {
14282 return [TOUCH_ACTION_MANIPULATION];
14283 },
14284
14285 process: function(input) {
14286 var options = this.options;
14287
14288 var validPointers = input.pointers.length === options.pointers;
14289 var validMovement = input.distance < options.threshold;
14290 var validTouchTime = input.deltaTime < options.time;
14291
14292 this.reset();
14293
14294 if ((input.eventType & INPUT_START) && (this.count === 0)) {
14295 return this.failTimeout();
14296 }
14297
14298 // we only allow little movement
14299 // and we've reached an end event, so a tap is possible
14300 if (validMovement && validTouchTime && validPointers) {
14301 if (input.eventType != INPUT_END) {
14302 return this.failTimeout();
14303 }
14304
14305 var validInterval = this.pTime ? (input.timeStamp - this.pTime < options.interval) : true;
14306 var validMultiTap = !this.pCenter || getDistance(this.pCenter, input.center) < options.posThreshold;
14307
14308 this.pTime = input.timeStamp;
14309 this.pCenter = input.center;
14310
14311 if (!validMultiTap || !validInterval) {
14312 this.count = 1;
14313 } else {
14314 this.count += 1;
14315 }
14316
14317 this._input = input;
14318
14319 // if tap count matches we have recognized it,
14320 // else it has began recognizing...
14321 var tapCount = this.count % options.taps;
14322 if (tapCount === 0) {
14323 // no failing requirements, immediately trigger the tap event
14324 // or wait as long as the multitap interval to trigger
14325 if (!this.hasRequireFailures()) {
14326 return STATE_RECOGNIZED;
14327 } else {
14328 this._timer = setTimeoutContext(function() {
14329 this.state = STATE_RECOGNIZED;
14330 this.tryEmit();
14331 }, options.interval, this);
14332 return STATE_BEGAN;
14333 }
14334 }
14335 }
14336 return STATE_FAILED;
14337 },
14338
14339 failTimeout: function() {
14340 this._timer = setTimeoutContext(function() {
14341 this.state = STATE_FAILED;
14342 }, this.options.interval, this);
14343 return STATE_FAILED;
14344 },
14345
14346 reset: function() {
14347 clearTimeout(this._timer);
14348 },
14349
14350 emit: function() {
14351 if (this.state == STATE_RECOGNIZED) {
14352 this._input.tapCount = this.count;
14353 this.manager.emit(this.options.event, this._input);
14354 }
14355 }
14356});
14357
14358/**
14359 * Simple way to create a manager with a default set of recognizers.
14360 * @param {HTMLElement} element
14361 * @param {Object} [options]
14362 * @constructor
14363 */
14364function NGHammer(element, options) {
14365 options = options || {};
14366 options.recognizers = ifUndefined(options.recognizers, NGHammer.defaults.preset);
14367 return new Manager(element, options);
14368}
14369
14370/**
14371 * @const {string}
14372 */
14373NGHammer.VERSION = '2.0.7';
14374
14375/**
14376 * default settings
14377 * @namespace
14378 */
14379NGHammer.defaults = {
14380 /**
14381 * set if DOM events are being triggered.
14382 * But this is slower and unused by simple implementations, so disabled by default.
14383 * @type {Boolean}
14384 * @default false
14385 */
14386 domEvents: false,
14387
14388 /**
14389 * The value for the touchAction property/fallback.
14390 * When set to `compute` it will magically set the correct value based on the added recognizers.
14391 * @type {String}
14392 * @default compute
14393 */
14394 touchAction: TOUCH_ACTION_COMPUTE,
14395
14396 /**
14397 * @type {Boolean}
14398 * @default true
14399 */
14400 enable: true,
14401
14402 /**
14403 * EXPERIMENTAL FEATURE -- can be removed/changed
14404 * Change the parent input target element.
14405 * If Null, then it is being set the to main element.
14406 * @type {Null|EventTarget}
14407 * @default null
14408 */
14409 inputTarget: null,
14410
14411 /**
14412 * force an input class
14413 * @type {Null|Function}
14414 * @default null
14415 */
14416 inputClass: null,
14417
14418 /**
14419 * Default recognizer setup when calling `NGHammer()`
14420 * When creating a new Manager these will be skipped.
14421 * @type {Array}
14422 */
14423 preset: [
14424 // RecognizerClass, options, [recognizeWith, ...], [requireFailure, ...]
14425 [RotateRecognizer, {enable: false}],
14426 [PinchRecognizer, {enable: false}, ['rotate']],
14427 [SwipeRecognizer, {direction: DIRECTION_HORIZONTAL}],
14428 [PanRecognizer, {direction: DIRECTION_HORIZONTAL}, ['swipe']],
14429 [TapRecognizer],
14430 [TapRecognizer, {event: 'doubletap', taps: 2}, ['tap']],
14431 [PressRecognizer]
14432 ],
14433
14434 /**
14435 * Some CSS properties can be used to improve the working of NGHammer.
14436 * Add them to this method and they will be set when creating a new Manager.
14437 * @namespace
14438 */
14439 cssProps: {
14440 /**
14441 * Disables text selection to improve the dragging gesture. Mainly for desktop browsers.
14442 * @type {String}
14443 * @default 'none'
14444 */
14445 userSelect: 'none',
14446
14447 /**
14448 * Disable the Windows Phone grippers when pressing an element.
14449 * @type {String}
14450 * @default 'none'
14451 */
14452 touchSelect: 'none',
14453
14454 /**
14455 * Disables the default callout shown when you touch and hold a touch target.
14456 * On iOS, when you touch and hold a touch target such as a link, Safari displays
14457 * a callout containing information about the link. This property allows you to disable that callout.
14458 * @type {String}
14459 * @default 'none'
14460 */
14461 touchCallout: 'none',
14462
14463 /**
14464 * Specifies whether zooming is enabled. Used by IE10>
14465 * @type {String}
14466 * @default 'none'
14467 */
14468 contentZooming: 'none',
14469
14470 /**
14471 * Specifies that an entire element should be draggable instead of its contents. Mainly for desktop browsers.
14472 * @type {String}
14473 * @default 'none'
14474 */
14475 userDrag: 'none',
14476
14477 /**
14478 * Overrides the highlight color shown when the user taps a link or a JavaScript
14479 * clickable element in iOS. This property obeys the alpha value, if specified.
14480 * @type {String}
14481 * @default 'rgba(0,0,0,0)'
14482 */
14483 tapHighlightColor: 'rgba(0,0,0,0)'
14484 }
14485};
14486
14487var STOP = 1;
14488var FORCED_STOP = 2;
14489
14490/**
14491 * Manager
14492 * @param {HTMLElement} element
14493 * @param {Object} [options]
14494 * @constructor
14495 */
14496function Manager(element, options) {
14497 this.options = assign({}, NGHammer.defaults, options || {});
14498
14499 this.options.inputTarget = this.options.inputTarget || element;
14500
14501 this.handlers = {};
14502 this.session = {};
14503 this.recognizers = [];
14504 this.oldCssProps = {};
14505
14506 this.element = element;
14507 this.input = createInputInstance(this);
14508 this.touchAction = new TouchAction(this, this.options.touchAction);
14509
14510 toggleCssProps(this, true);
14511
14512 each(this.options.recognizers, function(item) {
14513 var recognizer = this.add(new (item[0])(item[1]));
14514 item[2] && recognizer.recognizeWith(item[2]);
14515 item[3] && recognizer.requireFailure(item[3]);
14516 }, this);
14517}
14518
14519Manager.prototype = {
14520 /**
14521 * set options
14522 * @param {Object} options
14523 * @returns {Manager}
14524 */
14525 set: function(options) {
14526 assign(this.options, options);
14527
14528 // Options that need a little more setup
14529 if (options.touchAction) {
14530 this.touchAction.update();
14531 }
14532 if (options.inputTarget) {
14533 // Clean up existing event listeners and reinitialize
14534 this.input.destroy();
14535 this.input.target = options.inputTarget;
14536 this.input.init();
14537 }
14538 return this;
14539 },
14540
14541 /**
14542 * stop recognizing for this session.
14543 * This session will be discarded, when a new [input]start event is fired.
14544 * When forced, the recognizer cycle is stopped immediately.
14545 * @param {Boolean} [force]
14546 */
14547 stop: function(force) {
14548 this.session.stopped = force ? FORCED_STOP : STOP;
14549 },
14550
14551 /**
14552 * run the recognizers!
14553 * called by the inputHandler function on every movement of the pointers (touches)
14554 * it walks through all the recognizers and tries to detect the gesture that is being made
14555 * @param {Object} inputData
14556 */
14557 recognize: function(inputData) {
14558 var session = this.session;
14559 if (session.stopped) {
14560 return;
14561 }
14562
14563 // run the touch-action polyfill
14564 this.touchAction.preventDefaults(inputData);
14565
14566 var recognizer;
14567 var recognizers = this.recognizers;
14568
14569 // this holds the recognizer that is being recognized.
14570 // so the recognizer's state needs to be BEGAN, CHANGED, ENDED or RECOGNIZED
14571 // if no recognizer is detecting a thing, it is set to `null`
14572 var curRecognizer = session.curRecognizer;
14573
14574 // reset when the last recognizer is recognized
14575 // or when we're in a new session
14576 if (!curRecognizer || (curRecognizer && curRecognizer.state & STATE_RECOGNIZED)) {
14577 curRecognizer = session.curRecognizer = null;
14578 }
14579
14580 var i = 0;
14581 while (i < recognizers.length) {
14582 recognizer = recognizers[i];
14583
14584 // find out if we are allowed try to recognize the input for this one.
14585 // 1. allow if the session is NOT forced stopped (see the .stop() method)
14586 // 2. allow if we still haven't recognized a gesture in this session, or the this recognizer is the one
14587 // that is being recognized.
14588 // 3. allow if the recognizer is allowed to run simultaneous with the current recognized recognizer.
14589 // this can be setup with the `recognizeWith()` method on the recognizer.
14590 if (session.stopped !== FORCED_STOP && ( // 1
14591 !curRecognizer || recognizer == curRecognizer || // 2
14592 recognizer.canRecognizeWith(curRecognizer))) { // 3
14593 recognizer.recognize(inputData);
14594 } else {
14595 recognizer.reset();
14596 }
14597
14598 // if the recognizer has been recognizing the input as a valid gesture, we want to store this one as the
14599 // current active recognizer. but only if we don't already have an active recognizer
14600 if (!curRecognizer && recognizer.state & (STATE_BEGAN | STATE_CHANGED | STATE_ENDED)) {
14601 curRecognizer = session.curRecognizer = recognizer;
14602 }
14603 i++;
14604 }
14605 },
14606
14607 /**
14608 * get a recognizer by its event name.
14609 * @param {Recognizer|String} recognizer
14610 * @returns {Recognizer|Null}
14611 */
14612 get: function(recognizer) {
14613 if (recognizer instanceof Recognizer) {
14614 return recognizer;
14615 }
14616
14617 var recognizers = this.recognizers;
14618 for (var i = 0; i < recognizers.length; i++) {
14619 if (recognizers[i].options.event == recognizer) {
14620 return recognizers[i];
14621 }
14622 }
14623 return null;
14624 },
14625
14626 /**
14627 * add a recognizer to the manager
14628 * existing recognizers with the same event name will be removed
14629 * @param {Recognizer} recognizer
14630 * @returns {Recognizer|Manager}
14631 */
14632 add: function(recognizer) {
14633 if (invokeArrayArg(recognizer, 'add', this)) {
14634 return this;
14635 }
14636
14637 // remove existing
14638 var existing = this.get(recognizer.options.event);
14639 if (existing) {
14640 this.remove(existing);
14641 }
14642
14643 this.recognizers.push(recognizer);
14644 recognizer.manager = this;
14645
14646 this.touchAction.update();
14647 return recognizer;
14648 },
14649
14650 /**
14651 * remove a recognizer by name or instance
14652 * @param {Recognizer|String} recognizer
14653 * @returns {Manager}
14654 */
14655 remove: function(recognizer) {
14656 if (invokeArrayArg(recognizer, 'remove', this)) {
14657 return this;
14658 }
14659
14660 recognizer = this.get(recognizer);
14661
14662 // let's make sure this recognizer exists
14663 if (recognizer) {
14664 var recognizers = this.recognizers;
14665 var index = inArray(recognizers, recognizer);
14666
14667 if (index !== -1) {
14668 recognizers.splice(index, 1);
14669 this.touchAction.update();
14670 }
14671 }
14672
14673 return this;
14674 },
14675
14676 /**
14677 * bind event
14678 * @param {String} events
14679 * @param {Function} handler
14680 * @returns {EventEmitter} this
14681 */
14682 on: function(events, handler) {
14683 if (events === undefined) {
14684 return;
14685 }
14686 if (handler === undefined) {
14687 return;
14688 }
14689
14690 var handlers = this.handlers;
14691 each(splitStr(events), function(event) {
14692 handlers[event] = handlers[event] || [];
14693 handlers[event].push(handler);
14694 });
14695 return this;
14696 },
14697
14698 /**
14699 * unbind event, leave emit blank to remove all handlers
14700 * @param {String} events
14701 * @param {Function} [handler]
14702 * @returns {EventEmitter} this
14703 */
14704 off: function(events, handler) {
14705 if (events === undefined) {
14706 return;
14707 }
14708
14709 var handlers = this.handlers;
14710 each(splitStr(events), function(event) {
14711 if (!handler) {
14712 delete handlers[event];
14713 } else {
14714 handlers[event] && handlers[event].splice(inArray(handlers[event], handler), 1);
14715 }
14716 });
14717 return this;
14718 },
14719
14720 /**
14721 * emit event to the listeners
14722 * @param {String} event
14723 * @param {Object} data
14724 */
14725 emit: function(event, data) {
14726 // we also want to trigger dom events
14727 if (this.options.domEvents) {
14728 triggerDomEvent(event, data);
14729 }
14730
14731 // no handlers, so skip it all
14732 var handlers = this.handlers[event] && this.handlers[event].slice();
14733 if (!handlers || !handlers.length) {
14734 return;
14735 }
14736
14737 data.type = event;
14738 data.preventDefault = function() {
14739 data.srcEvent.preventDefault();
14740 };
14741
14742 var i = 0;
14743 while (i < handlers.length) {
14744 handlers[i](data);
14745 i++;
14746 }
14747 },
14748
14749 /**
14750 * destroy the manager and unbinds all events
14751 * it doesn't unbind dom events, that is the user own responsibility
14752 */
14753 destroy: function() {
14754 this.element && toggleCssProps(this, false);
14755
14756 this.handlers = {};
14757 this.session = {};
14758 this.input.destroy();
14759 this.element = null;
14760 }
14761};
14762
14763/**
14764 * add/remove the css properties as defined in manager.options.cssProps
14765 * @param {Manager} manager
14766 * @param {Boolean} add
14767 */
14768function toggleCssProps(manager, add) {
14769 var element = manager.element;
14770 if (!element.style) {
14771 return;
14772 }
14773 var prop;
14774 each(manager.options.cssProps, function(value, name) {
14775 prop = prefixed(element.style, name);
14776 if (add) {
14777 manager.oldCssProps[prop] = element.style[prop];
14778 element.style[prop] = value;
14779 } else {
14780 element.style[prop] = manager.oldCssProps[prop] || '';
14781 }
14782 });
14783 if (!add) {
14784 manager.oldCssProps = {};
14785 }
14786}
14787
14788/**
14789 * trigger dom event
14790 * @param {String} event
14791 * @param {Object} data
14792 */
14793function triggerDomEvent(event, data) {
14794 var gestureEvent = document.createEvent('Event');
14795 gestureEvent.initEvent(event, true, true);
14796 gestureEvent.gesture = data;
14797 data.target.dispatchEvent(gestureEvent);
14798}
14799
14800assign(NGHammer, {
14801 INPUT_START: INPUT_START,
14802 INPUT_MOVE: INPUT_MOVE,
14803 INPUT_END: INPUT_END,
14804 INPUT_CANCEL: INPUT_CANCEL,
14805
14806 STATE_POSSIBLE: STATE_POSSIBLE,
14807 STATE_BEGAN: STATE_BEGAN,
14808 STATE_CHANGED: STATE_CHANGED,
14809 STATE_ENDED: STATE_ENDED,
14810 STATE_RECOGNIZED: STATE_RECOGNIZED,
14811 STATE_CANCELLED: STATE_CANCELLED,
14812 STATE_FAILED: STATE_FAILED,
14813
14814 DIRECTION_NONE: DIRECTION_NONE,
14815 DIRECTION_LEFT: DIRECTION_LEFT,
14816 DIRECTION_RIGHT: DIRECTION_RIGHT,
14817 DIRECTION_UP: DIRECTION_UP,
14818 DIRECTION_DOWN: DIRECTION_DOWN,
14819 DIRECTION_HORIZONTAL: DIRECTION_HORIZONTAL,
14820 DIRECTION_VERTICAL: DIRECTION_VERTICAL,
14821 DIRECTION_ALL: DIRECTION_ALL,
14822
14823 Manager: Manager,
14824 Input: Input,
14825 TouchAction: TouchAction,
14826
14827 TouchInput: TouchInput,
14828 MouseInput: MouseInput,
14829 PointerEventInput: PointerEventInput,
14830 TouchMouseInput: TouchMouseInput,
14831 SingleTouchInput: SingleTouchInput,
14832
14833 Recognizer: Recognizer,
14834 AttrRecognizer: AttrRecognizer,
14835 Tap: TapRecognizer,
14836 Pan: PanRecognizer,
14837 Swipe: SwipeRecognizer,
14838 Pinch: PinchRecognizer,
14839 Rotate: RotateRecognizer,
14840 Press: PressRecognizer,
14841
14842 on: addEventListeners,
14843 off: removeEventListeners,
14844 each: each,
14845 merge: merge,
14846 extend: extend,
14847 assign: assign,
14848 inherit: inherit,
14849 bindFn: bindFn,
14850 prefixed: prefixed
14851});
14852
14853// this prevents errors when NGHammer is loaded in the presence of an AMD
14854// style loader but by script tag, not by the loader.
14855var freeGlobal = (typeof window !== 'undefined' ? window : (typeof self !== 'undefined' ? self : {})); // jshint ignore:line
14856freeGlobal.NGHammer = NGHammer;
14857
14858if (typeof define === 'function' && define.amdDISABLED) {
14859 define(function() {
14860 return NGHammer;
14861 });
14862} else if (typeof module != 'undefined' && module.exports) {
14863 module.exports = NGHammer;
14864} else {
14865 window[exportName] = NGHammer;
14866}
14867
14868})(window, document, 'NGHammer');
14869
14870
14871
14872
14873
14874
14875// END NANOGALLERY2
14876// }( jQuery )));
14877}));
14878
14879
14880//##########################################################################################################################
14881//##########################################################################################################################
14882//##########################################################################################################################
14883//##########################################################################################################################
14884//##########################################################################################################################
14885
14886// nanogallery2 auto start whithout javascript call
14887(function(){
14888 'use strict';
14889 jQuery(document).ready(function () {
14890
14891 // var t=document.querySelectorAll('[data-nanogallery2-portable]');
14892 // if( t.length > 0 ) {
14893 // portable mode
14894 // var link = document.createElement('link');
14895 // link.setAttribute("rel", "stylesheet");
14896 // link.setAttribute("type", "text/css");
14897 // link.onload = function(){
14898 // for( var i=0; i < t.length; i++ ) {
14899 // jQuery(t[i]).nanogallery2(jQuery(t[i]).data('nanogallery2-portable'));
14900 // }
14901 // }
14902 // link.setAttribute("href", '//nano.gallery/css/nanogallery2.css');
14903 // document.getElementsByTagName("head")[0].appendChild(link);
14904 // }
14905 // else {
14906 // standard mode
14907 var t=document.querySelectorAll('[data-nanogallery2]');
14908 for( var i=0; i < t.length; i++ ) {
14909 jQuery( t[i] ).nanogallery2( jQuery(t[i]).data('nanogallery2') );
14910 }
14911 // }
14912
14913 });
14914}).call(null);
14915
14916
14917/**!
14918 * @preserve nanogallery2 - NANOPHOTOSPROVIDER2 data provider
14919 * Homepage: http://nanogallery2.nanostudio.org
14920 * Sources: https://github.com/nanostudio-org/nanogallery2
14921 *
14922 * License: GPLv3 and commercial licence
14923 *
14924*/
14925
14926// ########################################################
14927// ##### nanogallery2 - module NANOPHOTOSPROVIDER2 #####
14928// ########################################################
14929
14930
14931(function (factory) {
14932 "use strict";
14933 if (typeof define === 'function' && define.amd) {
14934 // AMD. Register as an anonymous module.
14935 define(['jquery', 'nanogallery2'], factory);
14936 } else if (typeof exports === 'object' && typeof require === 'function') {
14937 // Browserify
14938 factory(require(['jquery', 'nanogallery2']));
14939 } else {
14940 // Browser globals
14941 factory(jQuery);
14942 }
14943}(function ($) {
14944// ;(function ($) {
14945
14946 jQuery.nanogallery2.data_nano_photos_provider2 = function (instance, fnName){
14947 var G = instance; // current nanogallery2 instance
14948
14949 /** @function AlbumGetContent */
14950 var AlbumGetContent = function(albumID, fnToCall, fnParam1, fnParam2) {
14951
14952 var albumIdx = NGY2Item.GetIdx(G, albumID);
14953
14954 // title is identical to ID (only for albums)
14955 if( instance.I[albumIdx].title == '' ) {
14956 instance.I[albumIdx].title = JsonConvertCharset(albumID);
14957 }
14958
14959 // Build the URL
14960 var url = G.O.dataProvider + '?albumID='+albumID; // which album
14961 // all thumbnails sizes (for responsive display)
14962 url += '&hxs=' + G.tn.settings.getH(G.GOM.curNavLevel, 'xs');
14963 url += '&wxs=' + G.tn.settings.getW(G.GOM.curNavLevel, 'xs');
14964 url += '&hsm=' + G.tn.settings.getH(G.GOM.curNavLevel, 'sm');
14965 url += '&wsm=' + G.tn.settings.getW(G.GOM.curNavLevel, 'sm');
14966 url += '&hme=' + G.tn.settings.getH(G.GOM.curNavLevel, 'me');
14967 url += '&wme=' + G.tn.settings.getW(G.GOM.curNavLevel, 'me');
14968 url += '&hla=' + G.tn.settings.getH(G.GOM.curNavLevel, 'la');
14969 url += '&wla=' + G.tn.settings.getW(G.GOM.curNavLevel, 'la');
14970 url += '&hxl=' + G.tn.settings.getH(G.GOM.curNavLevel, 'xl');
14971 url += '&wxl=' + G.tn.settings.getW(G.GOM.curNavLevel, 'xl');
14972 // url += '&wxs=' + G.tn.settings.width[G.GOM.curNavLevel].xs;
14973 // url += '&hxs=' + G.tn.settings.height[G.GOM.curNavLevel].xs;
14974 // url += '&wsm=' + G.tn.settings.width[G.GOM.curNavLevel].sm;
14975 // url += '&hsm=' + G.tn.settings.height[G.GOM.curNavLevel].sm;
14976 // url += '&wme=' + G.tn.settings.width[G.GOM.curNavLevel].me;
14977 // url += '&hme=' + G.tn.settings.height[G.GOM.curNavLevel].me;
14978 // url += '&wla=' + G.tn.settings.width[G.GOM.curNavLevel].la;
14979 // url += '&hla=' + G.tn.settings.height[G.GOM.curNavLevel].la;
14980 // url += '&wxl=' + G.tn.settings.width[G.GOM.curNavLevel].xl;
14981 // url += '&hxl=' + G.tn.settings.height[G.GOM.curNavLevel].xl;
14982
14983 PreloaderDisplay( true );
14984 jQuery.ajaxSetup({ cache: false });
14985 jQuery.support.cors = true;
14986 try {
14987
14988 var tId = setTimeout( function() {
14989 // workaround to handle JSONP (cross-domain) errors
14990 PreloaderDisplay(false);
14991 NanoAlert(G, 'Could not retrieve nanoPhotosProvider2 data (timeout).');
14992 }, 60000 );
14993
14994 if( G.O.debugMode ) { console.log('nanoPhotosProvider2 URL: ' + url); }
14995
14996 jQuery.getJSON(url, function(data, status, xhr) {
14997 clearTimeout( tId );
14998 PreloaderDisplay( false );
14999
15000 JsonParseData(albumIdx, data);
15001
15002 if( data.nano_status == 'ok' ) {
15003 AlbumPostProcess( albumID );
15004 if( fnToCall !== null && fnToCall !== undefined) {
15005 fnToCall( fnParam1, fnParam2, null );
15006 }
15007 }
15008 else {
15009 NanoAlert(G, 'Could not retrieve nanoPhotosProvider2 data. Error: ' + data.nano_status + ' - ' + data.nano_message);
15010 }
15011 })
15012 .fail( function(jqxhr, textStatus, error) {
15013 clearTimeout( tId );
15014 PreloaderDisplay( false );
15015
15016 var k=''
15017 for(var key in jqxhr) {
15018 k+= key + '=' + jqxhr[key] +'<br>';
15019 }
15020 var err = textStatus + ', ' + error + ' ' + k + '<br><br>URL:'+url;
15021 NanoAlert(G, 'Could not retrieve nanoPhotosProvider2 data. Error: ' + err);
15022
15023 });
15024
15025 }
15026 catch(e) {
15027 NanoAlert(G, 'Could not retrieve nanoPhotosProvider2 data. Error: ' + e);
15028 }
15029 }
15030
15031
15032 function JsonConvertCharset( str ) {
15033
15034 return decodeURIComponent(str);
15035
15036
15037 // Pb %C3%A9 --> %E9
15038 // in UTF-8: \u00e9=\xe9 (e9 = hex value)
15039 switch( G.O.dataCharset.toUpperCase() ) {
15040 case 'UTF-8': // Apache Windows
15041 return decodeURI(str); // do not use decodeURIComponent (would convert slash also)
15042 break;
15043 case 'Latin': // Apache Linux
15044 default :
15045 return escape(str);
15046 break;
15047 }
15048 }
15049
15050 function JsonParseData(albumIdx, data) {
15051 if( G.O.debugMode ) {
15052 console.log('nanoPhotosProvider2 parse data:');
15053 console.dir(data);
15054 }
15055
15056 var foundAlbumID = false;
15057 var nb = 0;
15058
15059 // loop each item
15060 jQuery.each( data.album_content, function( i, item ){
15061
15062 // base URL where the images are stored
15063 var baseURL = G.O.dataProvider.substring(0, G.O.dataProvider.indexOf('nano_photos_provider2.php'));
15064
15065 // image URL
15066 var src = baseURL + JsonConvertCharset( item.src );
15067
15068 // item title
15069 var title = item.title;
15070
15071 // item description ( '_' are replaced with ' ' )
15072 var description = item.description.split('_').join(' ');
15073
15074 // item kind ('album' or 'image')
15075 var kind = 'image';
15076 if( item.kind !== undefined && item.kind.length > 0 ) {
15077 kind = item.kind;
15078 }
15079
15080 // item ID
15081 var ID=item.ID;
15082
15083 var filterAlbum = false;
15084 if( kind == 'album' ) {
15085 // check if
15086 if( !FilterAlbumName(title, ID) ) { filterAlbum = true; }
15087 }
15088
15089 if( kind == 'image' || (kind == 'album' && FilterAlbumName(title, ID)) ) {
15090 var albumID = 0;
15091 if( item.albumID !== undefined ) {
15092 albumID = item.albumID;
15093 foundAlbumID = true;
15094 }
15095
15096 var tags = (item.tags === undefined) ? '' : item.tags;
15097
15098 var newItem = NGY2Item.New( G, title.split('_').join(' ') , description, ID, albumID, kind, tags );
15099 newItem.setMediaURL( src, 'img');
15100
15101 // dominant colorS as a gif
15102 if( item.dcGIF !== undefined ) {
15103 newItem.imageDominantColors='data:image/gif;base64,'+item.dcGIF;
15104 }
15105 // dominant color as hex rgb value
15106 if( item.dc !== undefined && item.dc !== '' ) {
15107 newItem.imageDominantColor=item.dc;
15108 }
15109
15110 if( kind == 'album' ) {
15111 // number of items in album
15112 newItem.numberItems = item.cnt;
15113 }
15114 else {
15115 // image size
15116 newItem.imageWidth = item.imgWidth;
15117 newItem.imageHeight = item.imgHeight;
15118 }
15119
15120 // item download URL
15121 if( item.originalURL != '' ) {
15122 newItem.downloadURL = baseURL+JsonConvertCharset(item.originalURL);
15123 }
15124
15125 // retrieve responsive thumbnails urls and sizes
15126 var cnl = G.GOM.curNavLevel; // current navigation level ('L1' or 'LN');
15127 var l=['xs', 'sm', 'me', 'la', 'xl'];
15128 for( var n = 0; n < l.length; n++ ) {
15129 newItem.thumbs.url[cnl][l[n]] = baseURL + JsonConvertCharset(item.t_url[n]);
15130 newItem.thumbs.width[cnl][l[n]] = parseInt(item.t_width[n]);
15131 newItem.thumbs.height[cnl][l[n]] = parseInt(item.t_height[n]);
15132 }
15133
15134 // post-process callback
15135 var fu = G.O.fnProcessData;
15136 if( fu !== null ) {
15137 typeof fu == 'function' ? fu(newItem, G.O.dataProvider, data) : window[fu](newItem, G.O.dataProvider, data);
15138 }
15139
15140 }
15141 });
15142
15143 G.I[albumIdx].contentIsLoaded = true; // album's content is ready
15144 }
15145
15146
15147 // -----------
15148 // Initialize
15149 function Init() {
15150
15151 }
15152
15153
15154 // shortcuts to NGY2Tools functions (with context)
15155 var PreloaderDisplay = NGY2Tools.PreloaderDisplay.bind(G);
15156 // var NanoAlert = NGY2Tools.NanoAlert.bind(G);
15157 var NanoAlert = NGY2Tools.NanoAlert;
15158 var GetImageTitleFromURL = NGY2Tools.GetImageTitleFromURL.bind(G);
15159 var FilterAlbumName = NGY2Tools.FilterAlbumName.bind(G);
15160 var AlbumPostProcess = NGY2Tools.AlbumPostProcess.bind(G);
15161
15162 switch( fnName ){
15163 case 'GetHiddenAlbums':
15164 break;
15165 case 'AlbumGetContent':
15166 var albumID = arguments[2],
15167 callback = arguments[3],
15168 cbParam1 = arguments[4],
15169 cbParam2 = arguments[5];
15170 AlbumGetContent(albumID, callback, cbParam1, cbParam2);
15171 break;
15172 case 'Init':
15173 Init();
15174 break;
15175 case '':
15176 break;
15177 }
15178
15179 };
15180
15181// END NANOPHOTOSPROVIDER DATA SOURCE FOR NANOGALLERY2
15182// }( jQuery ));
15183}));
15184
15185
15186
15187
15188/**!
15189 * @preserve nanogallery2 - GOOGLE PHOTOS data provider
15190 * Homepage: http://nanogallery2.nanostudio.org
15191 * Sources: https://github.com/nanostudio-org/nanogallery2
15192 *
15193 * License: GPLv3 and commercial licence
15194 *
15195*/
15196
15197// ###################################################
15198// ##### nanogallery2 - module for GOOGLE PHOTOS #####
15199// ##### requires nanogp #####
15200// ###################################################
15201
15202
15203(function (factory) {
15204 "use strict";
15205 if (typeof define === 'function' && define.amd) {
15206 // AMD. Register as an anonymous module.
15207 define(['jquery', 'nanogallery2'], factory);
15208 } else if (typeof exports === 'object' && typeof require === 'function') {
15209 // Browserify
15210 factory(require(['jquery', 'nanogallery2']));
15211 } else {
15212 // Browser globals
15213 factory(jQuery);
15214 }
15215}(function ($) {
15216// ;(function ($) {
15217
15218 jQuery.nanogallery2.data_google2 = function (instance, fnName){
15219 var G=instance; // current nanogallery2 instance
15220
15221
15222 /** @function AlbumGetContent */
15223 var AlbumGetContent = function(albumID, fnToCall, fnParam1, fnParam2) {
15224
15225 var url = '';
15226 var kind = 'image';
15227 var albumIdx = NGY2Item.GetIdx(G, albumID);
15228
15229 var maxResults='';
15230 if( G.galleryMaxItems.Get() > 0 ) {
15231 maxResults = '&max-results=' + G.galleryMaxItems.Get();
15232 }
15233
15234 var gat=''; // global authorization (using the BUILDER)
15235 if( typeof ngy2_pwa_at !== 'undefined' ) {
15236 gat=ngy2_pwa_at;
15237 }
15238
15239 if( albumID == 0 ) {
15240 // RETRIEVE THE LIST OF ALBUMS
15241 if( gat != '' ) {
15242 // in builder
15243 // url += '?alt=json&v=3&kind=album&deprecation-extension=true&thumbsize='+G.picasa.thumbSizes+maxResults+'&rnd=' + (new Date().getTime()) + '&access_token=' + gat;
15244 url = 'https://photoslibrary.googleapis.com/v1/albums';
15245 }
15246 else {
15247 // NANOGP2
15248 // url=G.O.google2URL + '?nguserid='+G.O.userID+'&alt=json&v=3&kind=album&thumbsize='+G.picasa.thumbSizes+maxResults+'&rnd=' + (new Date().getTime());
15249 url = G.O.google2URL + '?nguserid=' + G.O.userID + '&alt=json&v=3&kind=album' + maxResults + '&rnd=' + (new Date().getTime());
15250 }
15251 kind='album';
15252
15253 }
15254 else {
15255 // RETRIEVE THE CONTENT OF ONE ALBUM (=MEDIAS)
15256 if( gat != '' ) {
15257 // in builder
15258 // url += '/albumid/'+albumID+'?alt=json&kind=photo&deprecation-extension=true&thumbsize='+G.picasa.thumbSizes+maxResults+'&imgmax=d&access_token=' + gat;
15259 // url += '/albumid/'+albumID+'?alt=json&kind=photo&deprecation-extension=true&thumbsize='+G.picasa.thumbSizes+maxResults+'&imgmax=d&access_token=' + gat;
15260 url = 'https://photoslibrary.googleapis.com/v1/mediaItems:search';
15261 }
15262 else {
15263 // nanogp
15264 // url = G.O.google2URL + '?nguserid='+G.O.userID+'&ngalbumid='+albumID+'&alt=json&v=3&kind=photo&thumbsize='+G.picasa.thumbSizes+maxResults+'&imgmax=d';
15265 url = G.O.google2URL + '?nguserid=' + G.O.userID + '&ngalbumid=' + albumID + '&alt=json&v=3&kind=photo&' + maxResults;
15266 }
15267 }
15268
15269 if( G.O.debugMode ) { console.log('Google Photos URL: ' + url); }
15270
15271 PreloaderDisplay(true);
15272 jQuery.ajaxSetup({ cache: false });
15273 jQuery.support.cors = true;
15274 try {
15275 var tId = setTimeout( function() {
15276 // workaround to handle JSONP (cross-domain) errors
15277 PreloaderDisplay(false);
15278 NanoAlert('Could not retrieve AJAX data...');
15279 }, 60000 );
15280
15281 jQuery.getJSON( url + '&callback=?', function(data) {
15282
15283 if( data.nano_status == 'error' ) {
15284 clearTimeout(tId);
15285 PreloaderDisplay(false);
15286 NanoAlert(G, "Could not retrieve Google data. Error: " + data.nano_message);
15287 return;
15288 }
15289 clearTimeout(tId);
15290 PreloaderDisplay(false);
15291 GoogleParseData( albumIdx, kind, data );
15292 AlbumPostProcess(albumID);
15293 if( fnToCall !== null && fnToCall !== undefined) {
15294 fnToCall( fnParam1, fnParam2, null );
15295 }
15296
15297 })
15298 .fail( function(jqxhr, textStatus, error) {
15299 clearTimeout(tId);
15300 PreloaderDisplay(false);
15301
15302 var k=''
15303 for(var key in jqxhr) {
15304 k+= key + '=' + jqxhr[key] +'<br>';
15305 }
15306 var err = textStatus + ', ' + error + ' ' + k + '<br><br>URL:'+url;
15307 NanoAlert(G, "Could not retrieve Google data. Error: " + err);
15308 });
15309 }
15310 catch(e) {
15311 NanoAlert(G, "Could not retrieve Google data. Error: " + e);
15312 }
15313 }
15314
15315
15316 // -----------
15317 // Retrieve items from a Google Photos data stream
15318 // items can be images/viedos or albums
15319 function GoogleParseData(albumIdx, kind, data) {
15320
15321 if( G.O.debugMode ) {
15322 console.log('Google Photos data:');
15323 console.dir(data);
15324 }
15325 var albumID = G.I[albumIdx].GetID();
15326
15327 // iterate and parse each item
15328 jQuery.each(data, function(i,data){
15329
15330 if( typeof data === 'object' && data !== null ) { // only objects
15331
15332 var itemDescription = '';
15333 var itemTitle = '';
15334 if( kind == 'image') {
15335 itemTitle = data.description;
15336 }
15337 else {
15338 itemTitle = data.title;
15339 }
15340 if( itemTitle == undefined ) {
15341 // may happen...
15342 itemTitle = '';
15343 }
15344
15345 var itemID = data.id;
15346 if( kind == 'album' ) {
15347 if( !FilterAlbumName(itemTitle, itemID) || data.coverPhotoBaseUrl == undefined ) {
15348 return true;
15349 }
15350 }
15351
15352 // create ngy2 item
15353 var newItem = NGY2Item.New( G, itemTitle, itemDescription, itemID, albumID, kind, '' );
15354
15355 var width = 0;
15356 var height = 0;
15357
15358 // set the image src
15359 var src = '';
15360 if( kind == 'image' ) {
15361 src = data.baseUrl;
15362 if( !G.O.viewerZoom && G.O.viewerZoom != undefined ) {
15363 if( window.screen.width > window.screen.height ) {
15364 src += '=w' + window.screen.width;
15365 }
15366 else {
15367 src = s + '=h' + window.screen.height;
15368 }
15369 }
15370 else {
15371 // use full resolution image
15372 src += '=h' + data.mediaMetadata.height + '-w' + data.mediaMetadata.width;
15373
15374 // use original image
15375 // src += '=d';
15376 }
15377
15378 // image's URL
15379 newItem.setMediaURL( src, 'img');
15380
15381 // image size
15382 if( data.mediaMetadata.width !== undefined ) {
15383 newItem.imageWidth = parseInt(data.mediaMetadata.width);
15384 width = newItem.imageWidth;
15385 }
15386 if( data.mediaMetadata.height !== undefined ) {
15387 newItem.imageHeight=parseInt(data.mediaMetadata.height);
15388 height = newItem.imageHeight;
15389 }
15390
15391 // if( data.media$group != null && data.media$group.media$credit != null && data.media$group.media$credit.length > 0 ) {
15392 // newItem.author=data.media$group.media$credit[0].$t;
15393 // }
15394
15395 // Photo
15396 if( data.mediaMetadata.photo !== undefined ) {
15397 // exif data
15398 if( data.mediaMetadata.photo.exposureTime != undefined ) {
15399 newItem.exif.exposure = data.mediaMetadata.photo.exposureTime;
15400 }
15401 if( data.mediaMetadata.photo.focalLength != undefined ) {
15402 newItem.exif.focallength = data.mediaMetadata.photo.focalLength;
15403 }
15404 if( data.mediaMetadata.photo.apertureFNumber != undefined ) {
15405 newItem.exif.fstop = data.mediaMetadata.photo.apertureFNumber;
15406 }
15407 if( data.mediaMetadata.photo.isoEquivalent != undefined ) {
15408 newItem.exif.iso = data.mediaMetadata.photo.isoEquivalent;
15409 }
15410 if( data.mediaMetadata.photo.cameraModel != undefined ) {
15411 newItem.exif.model = data.mediaMetadata.photo.cameraModel;
15412 }
15413 }
15414
15415 // Video
15416 if( data.mediaMetadata.video !== undefined ) {
15417 if( data.mediaMetadata.video.cameraModel != undefined ) {
15418 newItem.exif.model = data.mediaMetadata.video.cameraModel;
15419 }
15420
15421 newItem.downloadURL = data.baseUrl + '=dv'; // set the download URL for the video
15422
15423 // newItem.mediaKind = 'selfhosted';
15424 // newItem.mediaMarkup = '<video controls class="nGY2ViewerMedia"><source src="'+ newItem.src +'" type="video/'+ 'video/mp4' +'" preload="auto">Your browser does not support the video tag (HTML 5).</video>';
15425 }
15426
15427 }
15428 else {
15429 // newItem.author = data.author[0].name.$t;
15430 newItem.numberItems = data.mediaItemsCount;
15431 }
15432
15433 // set the URL of the thumbnails images
15434 newItem.thumbs=GoogleThumbSetSizes2('l1', newItem.thumbs, data, kind, height, width );
15435 newItem.thumbs=GoogleThumbSetSizes2('lN', newItem.thumbs, data, kind,height ,width );
15436
15437 // post-process callback
15438 var fu = G.O.fnProcessData;
15439 if( fu !== null ) {
15440 typeof fu == 'function' ? fu(newItem, 'google2', data) : window[fu](newItem, 'google2', data);
15441 }
15442
15443 }
15444 });
15445
15446 G.I[albumIdx].contentIsLoaded = true; // album's content is ready
15447 }
15448
15449 // -----------
15450 // Set thumbnail sizes (width and height) and URLs (for all resolutions (xs, sm, me, la, xl) and levels (l1, lN)
15451 function GoogleThumbSetSizes2(level, tn, data, kind, height, width ) {
15452 var sizes=['xs','sm','me','la','xl'];
15453
15454 for(var i=0; i<sizes.length; i++ ) {
15455
15456 // media
15457 if( kind == 'image' ) {
15458 if( G.tn.settings.width[level][sizes[i]] == 'auto' ) {
15459 var ratio1 = width / height;
15460 tn.height[level][sizes[i]] = G.tn.settings.getH(level, sizes[i]);
15461 tn.width[level][sizes[i]] = G.tn.settings.getH(level, sizes[i]) * ratio1;
15462 tn.url[level][sizes[i]] = data.baseUrl + '=h' + G.tn.settings.getH(level, sizes[i]);
15463 continue;
15464 }
15465 if( G.tn.settings.height[level][sizes[i]] == 'auto' ) {
15466 var ratio1 = height / width;
15467 tn.width[level][sizes[i]] = G.tn.settings.getW(level, sizes[i]);
15468 tn.height[level][sizes[i]] = G.tn.settings.getW(level, sizes[i]) * ratio1;
15469 tn.url[level][sizes[i]] = data.baseUrl + '=w' + G.tn.settings.getW(level, sizes[i]);
15470 continue;
15471 }
15472
15473 tn.height[level][sizes[i]] = G.tn.settings.getH(level, sizes[i]);
15474 tn.width[level][sizes[i]] = G.tn.settings.getW(level, sizes[i]);
15475 tn.url[level][sizes[i]] = data.baseUrl + '=w' + G.tn.settings.getW(level, sizes[i]);
15476
15477 }
15478
15479 // album
15480 if( kind == 'album' ) {
15481 if( G.tn.settings.width[level][sizes[i]] == 'auto' ) {
15482 tn.url[level][sizes[i]]= data.coverPhotoBaseUrl + '=h' + G.tn.settings.getH(level, sizes[i]);
15483 continue;
15484 }
15485 if( G.tn.settings.height[level][sizes[i]] == 'auto' ) {
15486 tn.url[level][sizes[i]]= data.coverPhotoBaseUrl + '=w' + G.tn.settings.getW(level, sizes[i]);
15487 continue;
15488 }
15489 var w=G.tn.settings.mosaic[level + 'Factor']['w'][sizes[i]];
15490 tn.url[level][sizes[i]]= data.coverPhotoBaseUrl + '=h' + G.tn.settings.getH(level, sizes[i]) + '-w' + G.tn.settings.getW(level, sizes[i]);
15491
15492 }
15493 }
15494
15495 return tn;
15496 }
15497
15498
15499
15500 // -----------
15501 // Initialization
15502 function Init() {
15503 }
15504
15505
15506 // shortcuts to NGY2Tools functions (with context)
15507 var PreloaderDisplay = NGY2Tools.PreloaderDisplay.bind(G);
15508 // var NanoAlert = NGY2Tools.NanoAlert.bind(G);
15509 var NanoAlert = NGY2Tools.NanoAlert;
15510 var GetImageTitleFromURL = NGY2Tools.GetImageTitleFromURL.bind(G);
15511 var FilterAlbumName = NGY2Tools.FilterAlbumName.bind(G);
15512 var AlbumPostProcess = NGY2Tools.AlbumPostProcess.bind(G);
15513
15514 switch( fnName ){
15515 case 'AlbumGetContent':
15516 var albumID = arguments[2],
15517 callback2 = arguments[3],
15518 cbParam1 = arguments[4],
15519 cbParam2 = arguments[5];
15520 AlbumGetContent(albumID, callback2, cbParam1, cbParam2);
15521 break;
15522 case 'Init':
15523 Init();
15524 break;
15525 case '':
15526 break;
15527 }
15528
15529 };
15530
15531// END GOOGLE DATA SOURCE FOR NANOGALLERY2
15532// }( jQuery ));
15533}));
15534
15535
15536
15537
15538/**!
15539 * @preserve nanogallery2 - FLICKR data provider
15540 * Homepage: http://nanogallery2.nanostudio.org
15541 * Sources: https://github.com/nanostudio-org/nanogallery2
15542 *
15543 * License: GPLv3 and commercial licence
15544 *
15545*/
15546
15547// ############################################
15548// ##### nanogallery2 - module for FLICKR #####
15549// ############################################
15550
15551
15552(function (factory) {
15553 "use strict";
15554 if (typeof define === 'function' && define.amd) {
15555 // AMD. Register as an anonymous module.
15556 define(['jquery', 'nanogallery2'], factory);
15557 } else if (typeof exports === 'object' && typeof require === 'function') {
15558 // Browserify
15559 factory(require(['jquery', 'nanogallery2']));
15560 } else {
15561 // Browser globals
15562 factory(jQuery);
15563 }
15564}(function ($) {
15565// ;(function ($) {
15566
15567 jQuery.nanogallery2.data_flickr = function (instance, fnName){
15568 var G = instance; // current nanogallery2 instance
15569
15570 // ### Flickr
15571 // Details: http://www.flickr.com/services/api/misc.urls.html
15572 var Flickr = {
15573 url: function() {
15574 // Flickr API Going SSL-Only on June 27th, 2014
15575 return 'https://api.flickr.com/services/rest/';
15576 },
15577 thumbSize:' sq',
15578 thumbAvailableSizes : new Array(75, 100, 150, 240, 500, 640),
15579 thumbAvailableSizesStr : new Array('sq', 't', 'q', 's', 'm', 'z'),
15580 photoSize : '0',
15581 photoAvailableSizes : new Array(75, 100, 150, 240, 500, 640, 1024, 1024, 1600, 2048, 10000),
15582 photoAvailableSizesStr : new Array('sq', 't', 'q', 's', 'm', 'z', 'b', 'l', 'h', 'k', 'o'),
15583 ApiKey : "2f0e634b471fdb47446abcb9c5afebdc"
15584 };
15585
15586
15587 /** @function AlbumGetContent */
15588 var AlbumGetContent = function(albumID, fnToCall, fnParam1, fnParam2) {
15589
15590 var albumIdx = NGY2Item.GetIdx(G, albumID);
15591 var url = '';
15592 var kind = 'image';
15593 // photos
15594 if( G.O.photoset.toUpperCase() == 'NONE' || G.O.album.toUpperCase() == 'NONE' ) {
15595 // get photos from full photostream
15596 url = Flickr.url() + "?&method=flickr.people.getPublicPhotos&api_key=" + Flickr.ApiKey + "&user_id="+G.O.userID+"&extras=description,views,tags,url_o,url_sq,url_t,url_q,url_s,url_m,url_z,url_b,url_h,url_k&per_page=500&format=json";
15597 }
15598 else
15599 if( G.I[albumIdx].GetID() == 0 ) {
15600 // retrieve the list of albums
15601 url = Flickr.url() + "?&method=flickr.photosets.getList&api_key=" + Flickr.ApiKey + "&user_id="+G.O.userID+"&per_page=500&primary_photo_extras=tags,url_o,url_sq,url_t,url_q,url_s,url_m,url_l,url_z,url_b,url_h,url_k&format=json";
15602 kind='album';
15603 }
15604 else {
15605 // photos from one specific photoset
15606 url = Flickr.url() + "?&method=flickr.photosets.getPhotos&api_key=" + Flickr.ApiKey + "&photoset_id="+G.I[albumIdx].GetID()+"&extras=description,views,tags,url_o,url_sq,url_t,url_q,url_s,url_m,url_l,url_z,url_b,url_h,url_k&format=json";
15607 }
15608
15609 if( G.O.debugMode ) { console.log('Flickr URL: ' + url); }
15610
15611 PreloaderDisplay(true);
15612 jQuery.ajaxSetup({ cache: false });
15613 jQuery.support.cors = true;
15614
15615 var tId = setTimeout( function() {
15616 // workaround to handle JSONP (cross-domain) errors
15617 PreloaderDisplay(false);
15618 NanoAlert(G, 'Could not retrieve AJAX data...');
15619 }, 60000 );
15620
15621 var sourceData=[];
15622
15623 // Process the downloaded data
15624 var FlickrGetDone = function() {
15625 clearTimeout(tId);
15626 PreloaderDisplay(false);
15627
15628 if( kind == 'album' ) {
15629 FlickrParsePhotoSets(albumIdx, albumID, sourceData);
15630 }
15631 else {
15632 FlickrParsePhotos(albumIdx, albumID, sourceData);
15633 }
15634
15635 AlbumPostProcess(albumID);
15636 if( fnToCall !== null && fnToCall !== undefined) {
15637 fnToCall( fnParam1, fnParam2, null );
15638 }
15639 }
15640
15641 // download one page of data (=500 entries)
15642 var FlickrGetOnePage = function( url, page ) {
15643 jQuery.getJSON( url + '&page=' + page + '&jsoncallback=?', function(data, status, xhr) {
15644
15645 var pages=0;
15646 if( kind == 'album' ) {
15647 if( data.stat !== undefined && data.stat === 'fail' ) {
15648 NanoAlert(G, "Could not retrieve Flickr album list: " + data.message + " (code: "+data.code+").");
15649 return false;
15650 }
15651 sourceData=sourceData.concat(data.photosets.photoset);
15652 pages=data.photosets.pages;
15653 }
15654 else {
15655 if( G.O.photoset.toUpperCase() == 'NONE' || G.O.album.toUpperCase() == 'NONE' ) {
15656 // content of full photoset
15657 sourceData=sourceData.concat(data.photos.photo);
15658 pages=data.photos.pages;
15659 }
15660 else {
15661 // content of one album
15662 if( data.stat !== undefined && data.stat === 'fail' ) {
15663 NanoAlert(G, "Could not retrieve Flickr album: " + data.message + " (code: "+data.code+").");
15664 return false;
15665 }
15666 if( G.I[albumIdx].title == '' ) {
15667 G.I[albumIdx].title=data.photoset.title;
15668 }
15669 sourceData=sourceData.concat(data.photoset.photo);
15670 pages=data.photoset.pages;
15671 }
15672
15673 }
15674
15675 if( pages > page ) {
15676 FlickrGetOnePage(url, page+1);
15677 }
15678 else {
15679 FlickrGetDone();
15680 }
15681 })
15682 .fail( function(jqxhr, textStatus, error) {
15683 clearTimeout(tId);
15684 PreloaderDisplay(false);
15685 NanoAlert(G, "Could not retrieve Flickr ajax data: " + textStatus + ', ' + error);
15686 });
15687
15688 }
15689
15690 FlickrGetOnePage(url, 1);
15691
15692 }
15693
15694
15695
15696 // -----------
15697 // Retrieve items for one Flickr photoset
15698 function FlickrParsePhotos( albumIdx, albumID, source ) {
15699
15700 if( G.O.debugMode ) {
15701 console.log('Flickr parse photos:');
15702 console.dir(source);
15703 }
15704
15705 jQuery.each(source, function(i,item){
15706
15707 var itemID = item.id;
15708
15709 var imgUrl=item.url_sq; //fallback size
15710
15711 // get the title
15712 var itemTitle = item.title;
15713 if( G.O.thumbnailLabel.get('title') != '' ) {
15714 itemTitle=GetImageTitleFromURL(imgUrl);
15715 }
15716
15717 // get the description
15718 var itemDescription=item.description._content;
15719
15720 // retrieve the image size with highest available resolution
15721 var imgW=75, imgH=75;
15722 var start=Flickr.photoAvailableSizesStr.length-1;
15723 if( G.O.flickrSkipOriginal ) { start--; }
15724 for( var i = start; i>=0 ; i-- ) {
15725 if( item['url_'+Flickr.photoAvailableSizesStr[i]] != undefined ) {
15726 imgUrl=item['url_'+Flickr.photoAvailableSizesStr[i]];
15727 imgW=parseInt(item['width_'+Flickr.photoAvailableSizesStr[i]]);
15728 imgH=parseInt(item['height_'+Flickr.photoAvailableSizesStr[i]]);
15729 break;
15730 }
15731 }
15732
15733 var sizes = {};
15734 for( var p in item ) {
15735 if( p.indexOf('height_') == 0 || p.indexOf('width_') == 0 || p.indexOf('url_') == 0 ) {
15736 sizes[p]=item[p];
15737 }
15738 }
15739
15740 // tags
15741 var tags = item.tags !== undefined ? item.tags : '';
15742
15743 // create item
15744 var newItem = NGY2Item.New( G, itemTitle, itemDescription, itemID, albumID, 'image', tags );
15745
15746 // add image
15747 newItem.setMediaURL( imgUrl, 'img');
15748 newItem.imageWidth = imgW;
15749 newItem.imageHeight = imgH;
15750
15751
15752 // add thumbnails
15753 var tn = {
15754 url: { l1 : { xs:'', sm:'', me:'', la:'', xl:'' }, lN : { xs:'', sm:'', me:'', la:'', xl:'' } },
15755 width: { l1 : { xs:0, sm:0, me:0, la:0, xl:0 }, lN : { xs:0, sm:0, me:0, la:0, xl:0 } },
15756 height: { l1 : { xs:0, sm:0, me:0, la:0, xl:0 }, lN : { xs:0, sm:0, me:0, la:0, xl:0 } }
15757 };
15758 tn=FlickrRetrieveImages(tn, item, 'l1' );
15759 tn=FlickrRetrieveImages(tn, item, 'lN' );
15760 newItem.thumbs=tn;
15761
15762 // post-process callback
15763 var fu = G.O.fnProcessData;
15764 if( fu !== null ) {
15765 typeof fu == 'function' ? fu(newItem, 'flickr', item) : window[fu](newItem, 'flickr', item);
15766 }
15767
15768
15769 });
15770 G.I[albumIdx].contentIsLoaded=true;
15771
15772 }
15773
15774
15775
15776 // -----------
15777 // Retrieve the list of Flickr photosets
15778 function FlickrParsePhotoSets( albumIdx, albumID, source ) {
15779
15780 if( G.O.debugMode ) {
15781 console.log('Flickr parse list of albums:');
15782 console.dir(source);
15783 }
15784
15785 jQuery.each(source, function(i,item){
15786 //Get the title
15787 var itemTitle = item.title._content;
15788
15789 if( item.visibility_can_see_set == 0 ) { return true; } // skip it
15790
15791 if( FilterAlbumName(itemTitle, item.id) ) {
15792 var itemID=item.id;
15793 //Get the description
15794 var itemDescription = item.description._content != undefined ? item.description._content : '';
15795
15796 var sizes = {};
15797 for( var p in item.primary_photo_extras) {
15798 sizes[p] = item.primary_photo_extras[p];
15799 }
15800 var tags='';
15801 if( item.primary_photo_extras !== undefined ) {
15802 if( item.primary_photo_extras.tags !== undefined ) {
15803 tags = item.primary_photo_extras.tags;
15804 }
15805 }
15806
15807 var newItem = NGY2Item.New( G, itemTitle, itemDescription, itemID, albumID, 'album', tags );
15808 newItem.numberItems = item.photos;
15809 newItem.thumbSizes = sizes;
15810
15811 var tn = {
15812 url: { l1 : { xs:'', sm:'', me:'', la:'', xl:'' }, lN : { xs:'', sm:'', me:'', la:'', xl:'' } },
15813 width: { l1 : { xs:0, sm:0, me:0, la:0, xl:0 }, lN : { xs:0, sm:0, me:0, la:0, xl:0 } },
15814 height: { l1 : { xs:0, sm:0, me:0, la:0, xl:0 }, lN : { xs:0, sm:0, me:0, la:0, xl:0 } }
15815 };
15816 tn = FlickrRetrieveImages(tn, item.primary_photo_extras, 'l1' );
15817 tn = FlickrRetrieveImages(tn, item.primary_photo_extras, 'lN' );
15818 newItem.thumbs = tn;
15819
15820 // post-process callback
15821 var fu = G.O.fnProcessData;
15822 if( fu !== null ) {
15823 typeof fu == 'function' ? fu(newItem, 'flickr', item) : window[fu](newItem, 'flickr', item);
15824 }
15825
15826 }
15827 });
15828
15829 G.I[albumIdx].contentIsLoaded=true;
15830 }
15831
15832 function FlickrRetrieveImages(tn, item, level ) {
15833
15834 var sf=1;
15835 if( G.tn.opt[level].crop === true ) {
15836 sf=G.O.thumbnailCropScaleFactor;
15837 }
15838
15839
15840 var sizes=['xs','sm','me','la','xl'];
15841 for( var i=0; i<sizes.length; i++ ) {
15842 if( G.tn.settings.width[level][sizes[i]] == 'auto' || G.tn.settings.width[level][sizes[i]] == '' ) {
15843 var sdir='height_';
15844 var tsize=Math.ceil( G.tn.settings.height[level][sizes[i]] * G.tn.scale * sf * G.tn.settings.mosaic[level+'Factor']['h'][sizes[i]] );
15845 var one=FlickrRetrieveOneImage(sdir, tsize, item );
15846 tn.url[level][sizes[i]]=one.url;
15847 tn.width[level][sizes[i]]=one.width;
15848 tn.height[level][sizes[i]]=one.height;
15849 }
15850 else
15851 if( G.tn.settings.height[level][sizes[i]] == 'auto' || G.tn.settings.height[level][sizes[i]] == '' ) {
15852 var sdir='width_';
15853 var tsize=Math.ceil( G.tn.settings.width[level][sizes[i]] * G.tn.scale * sf * G.tn.settings.mosaic[level+'Factor']['w'][sizes[i]] );
15854 var one=FlickrRetrieveOneImage(sdir, tsize, item );
15855 tn.url[level][sizes[i]]=one.url;
15856 tn.width[level][sizes[i]]=one.width;
15857 tn.height[level][sizes[i]]=one.height;
15858 }
15859 else {
15860 var sdir='height_';
15861 var tsize=Math.ceil( G.tn.settings.height[level][sizes[i]] * G.tn.scale * sf * G.tn.settings.mosaic[level+'Factor']['h'][sizes[i]] );
15862 if( G.tn.settings.width[level][sizes[i]] > G.tn.settings.height[level][sizes[i]] ) {
15863 sdir='width_';
15864 tsize=Math.ceil( G.tn.settings.width[level][sizes[i]] * G.tn.scale * sf * G.tn.settings.mosaic[level+'Factor']['w'][sizes[i]] );
15865 }
15866 var one=FlickrRetrieveOneImage(sdir, tsize, item );
15867 tn.url[level][sizes[i]]=one.url;
15868 tn.width[level][sizes[i]]=one.width;
15869 tn.height[level][sizes[i]]=one.height;
15870 }
15871 }
15872 return tn;
15873 }
15874
15875 function FlickrRetrieveOneImage(sdir, tsize, item ) {
15876 var one={ url: '', width: 0, height: 0 };
15877 var tnIndex=0;
15878 for( var j=0; j < Flickr.thumbAvailableSizes.length; j++ ) {
15879 var size=item[sdir+Flickr.photoAvailableSizesStr[j]];
15880 if( size != undefined ) {
15881 tnIndex=j;
15882 if( size >= tsize ) {
15883 break;
15884 }
15885 }
15886 }
15887 var fSize=Flickr.photoAvailableSizesStr[tnIndex];
15888 one.url = item['url_'+fSize];
15889 one.width = parseInt(item['width_'+fSize]);
15890 one.height = parseInt(item['height_'+fSize]);
15891 return one;
15892 }
15893
15894
15895 /** @function GetHiddenAlbums */
15896 var GetHiddenAlbums = function( hiddenAlbums, callback ){
15897 // not supported -> doesn't exit in Flickr
15898 callback();
15899 }
15900
15901 // -----------
15902 // Initialize thumbnail sizes
15903 function Init() {
15904 return;
15905 }
15906
15907
15908 // shortcuts to NGY2Tools functions (with context)
15909 var PreloaderDisplay = NGY2Tools.PreloaderDisplay.bind(G);
15910 var NanoAlert = NGY2Tools.NanoAlert;
15911 var GetImageTitleFromURL = NGY2Tools.GetImageTitleFromURL.bind(G);
15912 var FilterAlbumName = NGY2Tools.FilterAlbumName.bind(G);
15913 var AlbumPostProcess = NGY2Tools.AlbumPostProcess.bind(G);
15914
15915 switch( fnName ){
15916 case 'GetHiddenAlbums':
15917 var hiddenAlbums = arguments[2],
15918 callback = arguments[3];
15919 GetHiddenAlbums(hiddenAlbums, callback);
15920 break;
15921 case 'AlbumGetContent':
15922 var albumID = arguments[2],
15923 callback = arguments[3],
15924 cbParam1 = arguments[4],
15925 cbParam2 = arguments[5];
15926 AlbumGetContent(albumID, callback, cbParam1, cbParam2);
15927 break;
15928 case 'Init':
15929 Init();
15930 break;
15931 case '':
15932 break;
15933 }
15934
15935 };
15936
15937// END FLICKR DATA SOURCE FOR NANOGALLERY2
15938// }( jQuery ));
15939}));
15940
15941
15942