MediaWiki:Gadget-GallerySlideshow.js

From EntropiaPlanets Wiki - Entropia Universe Guides Wiki Info

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Press Ctrl-F5.
/**
 * jQuery Galleriffic plugin
 *
 * Copyright (c) 2008 Trent Foley (http://trentacular.com)
 * Licensed under the MIT License:
 *   http://www.opensource.org/licenses/mit-license.php
 *
 * Much thanks to primary contributer Ponticlaro (http://www.ponticlaro.com)
 *
 * Rewritten for commons by [[User:DieBuche]],
 * Based on a script by [[User:Dschwen]]
 */
 
if (typeof(GallerySlide) == 'undefined' && wgNamespaceNumber == 14) {
 
importStylesheet('MediaWiki:Gadget-GallerySlideshow.css');
 
//Declare global var for todays specific gallery
var GallerySlide;
 
(function ($) {
	// Globally keep track of all images by their unique hash.  Each item is an image data object.
	var allImages = {};
	var imageCounter = 0;
 
	// Galleriffic static class
	$.galleriffic = {
		version: '2.1',
 
		// Strips invalid characters and any leading # characters
		normalizeHash: function (hash) {
			return hash.replace('#', '');
		},
 
		getImage: function (hash) {
			if (!hash) return undefined;
 
			hash = $.galleriffic.normalizeHash(hash);
			return allImages[hash];
		},
 
		// Global function that looks up an image by its hash and displays the image.
		// Returns false when an image is not found for the specified hash.
		// @param {String} hash This is the unique hash value assigned to an image.
		gotoImage: function (hash) {
			var imageData = $.galleriffic.getImage(hash);
			//if (!imageData) return false;
			var gallery = imageData.gallery;
			gallery.gotoImage(imageData);
 
			return true;
		}
 
	};
 
	var defaults = {
		delay: 2500,
		preloadAhead: 25,
		renderSSControls: true,
		renderNavControls: true,
		playLinkText: 'Play',
		pauseLinkText: 'Pause',
		prevLinkText: 'Previous',
		nextLinkText: 'Next',
		hideText: 'Close slideshow',
		enableKeyboardNavigation: true,
		autoStart: false,
		defaultTransitionDuration: 700,
		onSlideChange: function (prevIndex, nextIndex) {
 
			var displayed = Math.floor(this.$thumbsUl.parent().width() / 81);
			var spaceRight = displayed - (nextIndex - this.hiddenLeft);
			var spaceLeft = (1 + nextIndex - this.hiddenLeft);
			if (spaceRight < 3) {
				//Time to slide viewport
				var current = this.$thumbsUl.css('left').replace('px', '');
				var offset = (parseFloat(current) - (3 - spaceRight) * 81);
				this.$thumbsUl.animate({
					left: offset + 'px'
				}, 'fast');
				this.hiddenLeft = this.hiddenLeft + (3 - spaceRight);
			}
			if (spaceLeft < 3 && nextIndex > 1) {
				var current = this.$thumbsUl.css('left').replace('px', '');
				var offset = (parseFloat(current) + (3 - spaceLeft) * 81);
				this.$thumbsUl.animate({
					left: offset + 'px'
				}, 'fast');
				this.hiddenLeft = this.hiddenLeft - (3 - spaceLeft);
			}
 
			if (nextIndex == 0) {
				this.$thumbsUl.animate({
					left: '0px'
				}, 'fast');
				this.hiddenLeft = 0;
			}
 
			if (this.data.length - 5 < nextIndex) {
				//Time to fetch more
				GallerySlide.queryApi();
			}
		},
		// accepts a delegate like such: function(prevIndex, nextIndex) { ... }
		onTransitionOut: undefined,
		// accepts a delegate like such: function(slide, caption, isSync, callback) { ... }
		onTransitionIn: undefined
	};
 
	// Primary Galleriffic initialization function that should be called on the thumbnail container.
	$.fn.galleriffic = function (settings) {
		//  Extend Gallery Object
		$.extend(this, {
			// Returns the version of the script
			version: $.galleriffic.version,
 
			// Current state of the slideshow
			isSlideshowRunning: false,
			slideshowTimeout: undefined,
			hiddenLeft: 0,
			apiURL: wgServer + wgScriptPath + "/api.php",
			initial: true,
			data: [],
 
			// This function is attached to the click event of generated hyperlinks within the gallery
			clickHandler: function (e, link) {
				this.pause();
 
				// The href attribute holds the unique hash for an image
				var hash = $.galleriffic.normalizeHash($(link).attr('href'));
				$.galleriffic.gotoImage(hash);
				e.preventDefault();
 
			},
 
			createContainer: function () {
				var $d = $('<div class="slideshow-container"></div>');
 
				this.$imageContainer = $('<div id="slideshow" class="slideshow"></div>');
				this.$captionContainer = $('<div id="caption" class="caption-container"></div>');
				this.$loadingContainer = $('<div id="loading" class="loader"></div>');
				this.$controlsContainer = $('<div id="controls" class="controls"></div>');
 
				this.append('<div id="thumbs" class="navigation"><ul class="thumbs"></ul></div>');
				this.append($d);
				$d.append(this.$controlsContainer).append(this.$loadingContainer).append(this.$imageContainer);
				this.append(this.$captionContainer);
 
				this.$thumbsUl = this.find('ul.thumbs');
			},
			// Scrapes the thumbnail container for thumbs and adds each to the gallery
			initializeThumbs: function () {
 
				var data = this.passedData;
 
				var gallery = this;
 
				for (i in data) {
 
					imageData = data[i];
 
					imageData.index = hash = imageCounter;
					imageData.gallery = gallery;
 
					var aspect = (imageData.width / imageData.height);
					var size = (aspect > 1) ? 'height' : 'width';
					var $thumb = $('<li><a class="thumb"><img ' + size + '=75px src="' + imageData.slideUrl + '" /></a></li>');
					$thumb.css('opacity', 0.67).hover(
 
					function () {
						$(this).not('.selected').fadeTo('fast', 1);
					}, function () {
						$(this).not('.selected').fadeTo('fast', 0.67);
					});
 
					this.$thumbsUl.append($thumb);
 
					imageData.caption = '<a href=' + wgServer + '/wiki/' + imageData.title.replace(/ /g, '_') + '>' + imageData.title.replace('File:', '').replace(/\.[\w]{3,4}$/, '') +'</a>';
 
					// Register the image globally
					allImages['' + hash] = imageData;
 
					// Setup attributes and click handler
					$thumb.find('a').attr('href', '#' + hash).removeAttr('name').click(function (e) {
						gallery.clickHandler(e, this);
					});
					imageCounter++;
 
				}
				this.data = this.data.concat(data);
 
				return this;
			},
 
			isPreloadComplete: false,
 
			// Initalizes the image preloader
			preloadInit: function () {
				if (this.preloadAhead == 0) return this;
 
				this.preloadStartIndex = this.currentImage.index;
				var nextIndex = this.getNextIndex(this.preloadStartIndex);
				return this.preloadRecursive(this.preloadStartIndex, nextIndex);
			},
 
			// Changes the location in the gallery the preloader should work
			// @param {Integer} index The index of the image where the preloader should restart at.
			preloadRelocate: function (index) {
				// By changing this startIndex, the current preload script will restart
				this.preloadStartIndex = index;
				return this;
			},
 
			// Recursive function that performs the image preloading
			// @param {Integer} startIndex The index of the first image the current preloader started on.
			// @param {Integer} currentIndex The index of the current image to preload.
			preloadRecursive: function (startIndex, currentIndex) {
				// Check if startIndex has been relocated
				if (startIndex != this.preloadStartIndex) {
					var nextIndex = this.getNextIndex(this.preloadStartIndex);
					return this.preloadRecursive(this.preloadStartIndex, nextIndex);
				}
 
				var gallery = this;
 
				// Now check for preloadAhead count
				var preloadCount = currentIndex - startIndex;
				if (preloadCount < 0) preloadCount = this.data.length - 1 - startIndex + currentIndex;
				if (this.preloadAhead >= 0 && preloadCount > this.preloadAhead) {
					// Do this in order to keep checking for relocated start index
					setTimeout(function () {
						gallery.preloadRecursive(startIndex, currentIndex);
					}, 500);
					return this;
				}
 
				var imageData = this.data[currentIndex];
				if (!imageData) return this;
 
				// If already loaded, continue
				if (imageData.image) return this.preloadNext(startIndex, currentIndex);
 
				// Preload the image
				var image = new Image();
 
				image.onload = function () {
					imageData.image = this;
					gallery.preloadNext(startIndex, currentIndex);
				};
 
				image.alt = imageData.title;
				image.src = imageData.slideUrl;
 
				return this;
			},
 
			// Called by preloadRecursive in order to preload the next image after the previous has loaded.
			// @param {Integer} startIndex The index of the first image the current preloader started on.
			// @param {Integer} currentIndex The index of the current image to preload.
			preloadNext: function (startIndex, currentIndex) {
				var nextIndex = this.getNextIndex(currentIndex);
				if (nextIndex == startIndex) {
					this.isPreloadComplete = true;
				} else {
					// Use setTimeout to free up thread
					var gallery = this;
					setTimeout(function () {
						gallery.preloadRecursive(startIndex, nextIndex);
					}, 100);
				}
 
				return this;
			},
 
			// Safe way to get the next image index relative to the current image.
			// If the current image is the last, returns 0
			getNextIndex: function (index) {
				var nextIndex = index + 1;
				if (nextIndex >= this.data.length) nextIndex = 0;
				return nextIndex;
			},
 
			// Safe way to get the previous image index relative to the current image.
			// If the current image is the first, return the index of the last image in the gallery.
			getPrevIndex: function (index) {
				var prevIndex = index - 1;
				if (prevIndex < 0) prevIndex = this.data.length - 1;
				return prevIndex;
			},
 
			// Pauses the slideshow
			pause: function () {
				this.isSlideshowRunning = false;
				if (this.slideshowTimeout) {
					clearTimeout(this.slideshowTimeout);
					this.slideshowTimeout = undefined;
				}
 
				if (this.$controlsContainer) {
					this.$controlsContainer.find('div.ss-controls a').removeClass().addClass('play').attr('title', this.playLinkText).attr('href', '#play').html(this.playLinkText);
				}
 
				return this;
			},
 
			// Plays the slideshow
			play: function () {
				this.isSlideshowRunning = true;
 
				if (this.$controlsContainer) {
					this.$controlsContainer.find('div.ss-controls a').removeClass().addClass('pause').attr('title', this.pauseLinkText).attr('href', '#pause').html(this.pauseLinkText);
				}
 
				if (!this.slideshowTimeout) {
					var gallery = this;
					this.slideshowTimeout = setTimeout(function () {
						gallery.next(true);
					}, this.delay);
				}
 
				return this;
			},
 
			// Toggles the state of the slideshow (playing/paused)
			toggleSlideshow: function () {
				if (this.isSlideshowRunning) this.pause();
				else this.play();
 
				return this;
			},
			// Advances the gallery to the next image.
			// @param {Boolean} dontPause Specifies whether to pause the slideshow.
			next: function (dontPause) {
				this.gotoIndex(this.getNextIndex(this.currentImage.index), dontPause);
				return this;
			},
 
			// Navigates to the previous image in the gallery.
			// @param {Boolean} dontPause Specifies whether to pause the slideshow.
			previous: function (dontPause) {
				this.gotoIndex(this.getPrevIndex(this.currentImage.index), dontPause);
				return this;
			},
 
			// Navigates to the image at the specified index in the gallery
			// @param {Integer} index The index of the image in the gallery to display.
			// @param {Boolean} dontPause Specifies whether to pause the slideshow.
			gotoIndex: function (index, dontPause) {
				if (!dontPause) this.pause();
 
				if (index < 0) index = 0;
				else if (index >= this.data.length) index = this.data.length - 1;
 
				var imageData = this.data[index];
 
				this.gotoImage(imageData);
 
				return this;
			},
 
			// This function is guaranteed to be called anytime a gallery slide changes.
			// @param {Object} imageData An object holding the image metadata of the image to navigate to.
			gotoImage: function (imageData) {
				var index = imageData.index;
 
				if (this.onSlideChange) this.onSlideChange(this.currentImage.index, index);
 
				this.currentImage = imageData;
				this.preloadRelocate(index);
 
				this.refresh();
 
				return this;
			},
 
			// Returns the default transition duration value.  The value is halved when not
			// performing a synchronized transition.
			// @param {Boolean} isSync Specifies whether the transitions are synchronized.
			getDefaultTransitionDuration: function (isSync) {
				if (isSync) return this.defaultTransitionDuration;
				return this.defaultTransitionDuration / 2;
			},
 
			// Rebuilds the slideshow image and controls and performs transitions
			refresh: function () {
				var imageData = this.currentImage;
				if (!imageData) return this;
 
				var index = imageData.index;
 
				var previousSlide = this.$imageContainer.find('span.current').addClass('previous').removeClass('current');
				var previousCaption = 0;
 
				if (this.$captionContainer) {
					previousCaption = this.$captionContainer.find('span.current').addClass('previous').removeClass('current');
				}
 
				// Perform transitions simultaneously if the next image is already preloaded
				var isSync = imageData.image;
 
				// Flag we are transitioning
				var isTransitioning = true;
				var gallery = this;
 
				var transitionOutCallback = function () {
					// Flag that the transition has completed
					isTransitioning = false;
 
					// Remove the old slide
					previousSlide.remove();
 
					// Remove old caption
					if (previousCaption) previousCaption.remove();
 
					if (!isSync) {
						if (imageData.image && imageData.index == gallery.data[gallery.currentImage.index].index) {
							gallery.buildImage(imageData, isSync);
						} else {
							// Show loading container
							if (gallery.$loadingContainer) {
								gallery.$loadingContainer.show();
							}
						}
					}
				};
 
				if (previousSlide.length == 0) {
					// For the first slide, the previous slide will be empty, so we will call the callback immediately
					transitionOutCallback();
				} else {
 
					previousSlide.fadeTo(this.getDefaultTransitionDuration(isSync), 0.0, transitionOutCallback);
					if (previousCaption) previousCaption.fadeTo(this.getDefaultTransitionDuration(isSync), 0.0);
 
				}
 
				// Go ahead and begin transitioning in of next image
				if (isSync) this.buildImage(imageData, isSync);
 
				if (!imageData.image) {
					var image = new Image();
 
					// Wire up mainImage onload event
					image.onload = function () {
						imageData.image = this;
 
						// Only build image if the out transition has completed and we are still on the same image hash
						if (!isTransitioning && imageData.index == gallery.data[gallery.currentImage.index].index) {
							gallery.buildImage(imageData, isSync);
						}
					};
 
					// set alt and src
					image.alt = imageData.title;
					image.src = imageData.slideUrl;
				}
 
				// This causes the preloader (if still running) to relocate out from the currentIndex
				this.relocatePreload = true;
 
				return this.syncThumbs();
			},
 
			// Called by the refresh method after the previous image has been transitioned out or at the same time
			// as the out transition when performing a synchronous transition.
			// @param {Object} imageData An object holding the image metadata of the image to build.
			// @param {Boolean} isSync Specifies whether the transitions are synchronized.
			buildImage: function (imageData, isSync) {
				var gallery = this;
				var nextIndex = this.getNextIndex(imageData.index);
 
				var left = (gallery.$thumbsUl.parent().width() - imageData.width) / 2;
 
				// Construct new hidden span for the image
				var newSlide = this.$imageContainer.append('<span class="image-wrapper current" style="left:' + left + 'px ;"><a class="advance-link"  href="#' + this.data[nextIndex].index + '" title="' + imageData.title + '">&nbsp;</a></span>').find('span.current').css('opacity', '0');
 
				newSlide.find('a').append(imageData.image).click(function (e) {
					gallery.clickHandler(e, this);
				});
 
				var newCaption = 0;
				if (this.$captionContainer) {
					// Construct new hidden caption for the image
					newCaption = this.$captionContainer.append('<span class="image-caption current" style="left:' + left + 'px;"></span>').find('span.current').css('opacity', '0').append(imageData.caption);
				}
 
				// Hide the loading conatiner
				if (this.$loadingContainer) {
					this.$loadingContainer.hide();
				}
 
				// Transition in the new image
				if (this.onTransitionIn) {
					this.onTransitionIn(newSlide, newCaption, isSync);
				} else {
					newSlide.fadeTo(this.getDefaultTransitionDuration(isSync), 1.0);
					if (newCaption) newCaption.fadeTo(this.getDefaultTransitionDuration(isSync), 1.0);
				}
 
				if (this.isSlideshowRunning) {
					if (this.slideshowTimeout) clearTimeout(this.slideshowTimeout);
 
					this.slideshowTimeout = setTimeout(function () {
						gallery.next(true);
					}, this.delay);
				}
 
				return this;
			},
 
 
			// Applies the selected class to the current image's corresponding thumbnail.
			// Also checks if the current page has changed and updates the displayed page of thumbnails if necessary.
			syncThumbs: function () {
				// Remove existing selected class and add selected class to new thumb
				var $thumbs = this.$thumbsUl.children();
 
				$thumbs.filter('.selected').removeClass('selected').fadeTo('fast', 0.67);
				$thumbs.eq(this.currentImage.index).addClass('selected').fadeTo('fast', 1);
 
				return this;
			},
 
			init: function () {
				this.createContainer();
 
				// Initialize the thumbails
				this.initializeThumbs();
 
				this.currentImage = this.data[0];
 
				var gallery = this;
 
				// Hide the loadingContainer
				this.$loadingContainer.hide();
				this.gotoIndex(0);
 
				// Setup controls
				if (this.renderSSControls) {
					if (this.autoStart) {
						this.$controlsContainer.append('<div class="ss-controls"><a href="#pause" class="pause" title="' + this.pauseLinkText + '">' + this.pauseLinkText + '</a></div>');
					} else {
						this.$controlsContainer.append('<div class="ss-controls"><a href="#play" class="play" title="' + this.playLinkText + '">' + this.playLinkText + '</a></div>');
					}
 
					this.$controlsContainer.find('div.ss-controls a').click(function (e) {
						gallery.toggleSlideshow();
						e.preventDefault();
						return false;
					});
				}
 
				if (this.renderNavControls) {
					this.$controlsContainer.append('<div class="nav-controls"><a class="prev" title="' + this.prevLinkText + '">' + this.prevLinkText + '</a><a class="next" title="' + this.nextLinkText + '">' + this.nextLinkText + '</a><a class="toggle" title="' + this.hideText + '">' + this.hideText + '</a></div>');
 
					this.$controlsContainer.find('div.nav-controls a.prev').click(function () {
						gallery.previous();
					});
					this.$controlsContainer.find('div.nav-controls a.next').click(function () {
						gallery.next();
					});
					this.$controlsContainer.find('div.nav-controls a.toggle').click(function () {
						gallery.toggleVisibility();
					});
				}
 
 
				// Setup Keyboard Navigation
				if (this.enableKeyboardNavigation) {
					$(document).keydown(function (e) {
						var key = e.charCode ? e.charCode : e.keyCode ? e.keyCode : 0;
						switch (key) {
						case 32:
							// space
							gallery.next();
							e.preventDefault();
							break;
						case 35:
							// End
							gallery.gotoIndex(gallery.data.length - 1);
							e.preventDefault();
							break;
						case 37:
							// left arrow
							gallery.previous();
							e.preventDefault();
							break;
						case 39:
							// right arrow
							gallery.next();
							e.preventDefault();
							break;
						}
					});
				}
				// Auto start the slideshow
				if (this.autoStart) this.play();
 
				// Kickoff Image Preloader after 1 second
				setTimeout(function () {
					gallery.preloadInit();
				}, 1000);
			},
 
			start: function () {
				$('#GallerySlideInit').hide();
				$('#SlideContainer').animate({
					height: '600px'
				});
				this.queryApi();
 
			},
			toggleVisibility: function () {
				$('#GallerySlideInit').toggle().unbind('click').click(GallerySlide.toggleVisibility);
				$('#SlideContainer').toggle();
 
			},
 
			queryApi: function () {
				params = {
					action: 'query',
					generator: 'categorymembers',
					gcmtitle: wgPageName,
					gcmlimit: Math.floor($('#mw-category-media').width() / 81) + 1,
					gcmnamespace: 6,
					prop: 'imageinfo',
					iiprop: 'url',
					iiurlwidth: '1500',
					iiurlheight: '400',
					format: 'json'
				};
 
				if (this.cont) params.gcmcontinue = this.cont;
				if (!this.initial && !this.cont) return;
 
				$.ajax({
					url: this.apiURL,
					cache: false,
					dataType: 'json',
					data: params,
					type: 'POST',
					success: function (result, status, x) {
						GallerySlide.processReturn(result);
					}
				});
 
			},
 
			processReturn: function (result) {
				var pages = result.query.pages;
				data = [];
				i = 0;
 
				if (result['query-continue']) this.cont = result['query-continue'].categorymembers.gcmcontinue;
				else this.cont = false;
 
				for (var id in pages) {
					v = pages[id];
					n = data[i] = {};
 
					n.title = v.title;
					n['link'] = v.imageinfo[0].descriptionurl;
					n.slideUrl = v.imageinfo[0].thumburl;
					n.width = v.imageinfo[0].thumbwidth;
					n.height = v.imageinfo[0].thumbheight;
					i++;
				}
 
				this.passedData = data;
 
				if (this.initial) {
					this.init();
				} else {
					this.initializeThumbs();
				}
				this.initial = false;
			}
		});
 
		// Now initialize the gallery
		$.extend(this, defaults, settings);
 
		return this;
	};
})(jQuery);
 
$j(document).ready(function () {
	$('#mw-category-media h2').after('<span id="GallerySlideInit">Show Slideshow</span>');
	$('#GallerySlideInit').after('<div id="SlideContainer" style="width:100%; height:0px; background: #EBEBEB; "></div>');
 
	GallerySlide = $('#SlideContainer').galleriffic();
 
	$('#GallerySlideInit').click(function () {
		GallerySlide.start();
	});
});
 
}