Minecraft Wiki
Advertisement
In andere talen

Let op! Nadat u de wijzigingen hebt opgeslagen is het wellicht nodig uw browsercache te legen.

  • Firefox / Safari: houd ⇧ Shift ingedrukt terwijl u op Vernieuwen klikt of druk op Ctrl-F5 of Ctrl-R (-⇧ Shift-R op een Mac)
  • Google Chrome: druk op Ctrl-⇧ Shift-R (-⇧ Shift-R op een Mac)
  • Internet Explorer: houd Ctrl ingedrukt terwijl u op Vernieuwen klikt of druk op Ctrl-F5
  • Opera: leeg uw cache in Extra → Voorkeuren
/** 
 * @title SpriteDoc
 * @version 1.1.0
 * @author DarkShadowTNT
 * @license CC-BY-SA-4.0
 * 
 * @dependencies mediawiki.api, mediawiki.notify, mediawiki.notification
 *				 mediawiki.util
 */
'use-strict';

/** Set some variables first, to ensure the script will work as intended.    **/

var i18n = {
	/**
	 * loadsprite 'element'. Example: <loadsprite sprite="{{{1}}}"/>, where:
	 * 
	 * @key loadspriteElement - corresponds to 'loadsprite' in example.
	 * @key loadspriteSpriteAttribute - corresponds to 'sprite' in example.
	 */
	loadspriteElement: 'loadsprite',
	loadspriteSpriteAttribute: 'sprite',
	
	/**
	 * Sprite module. This is the main module where sprite data is loaded.
	 * 
	 * @key spriteModule - corresponds to the name of the sprite module.
	 * @key spriteModuleGetRawSpriteDataFunction - corresponds to the name of
	 *					the function which loads the raw sprite data from the
	 *					module.
	 * @key spriteSuffix - Default suffix for the spritesheet image.
	 */
	spriteModule: 'sprite',
	spriteModuleGetRawSpriteDataFunction: 'getRawSpriteData',
	spriteSuffix: 'CSS.png',
	
	/**
	 * FileUrl template.
	 * @key fileUrlTemplate - name of the FileUrl template.
	 */
	fileUrlTemplate: 'FileUrl',
	 
	 /**
	  * Error messages to show when things go wrong.
	  * 
	  * @key warning - Title of all warning notification.
	  * @key loadingSpriteDataFailed - Warning message to show when sprite data
	  * 							   could not be loaded.
	  * @key loadingSpriteImageFailed - Warning message to show when the sprite
	  * 							    sheet could not be loaded.
	  */
	 warning: 'Waarschuwing',
	 loadingSpriteDataFailed: 'Spritedata kon niet geladen worden.',
	 loadingSpriteImageFailed: 'Spritesheet kon niet geladen worden.',
	 
	 /**
	  * i18n for the table of contents.
	  * 
	  * @key TOCText - Title of the table of contents.
	  */
	 TOCText: 'Inhoud',
};

var spritedocElement = document.getElementById('spritedoc');

var sprites = [];

var spriteDataIds; // This variable will contain all sprite data
var spriteDataSections;
var spriteDataSettings;

var spritesheetUrl;

var currentTimestamp = Date.now();

// These variables are for the sprite editor to not load too soon
var lastSection;
var lastBox;

// Sprite defaults. Values will be overridden with values from the settings object in the API call.
var spriteDefaults = {
	scale: 1,
	sheetsize: 256,
	size: 16,
	pos: 1,
	align: 'text-top',
};

/** Actual spritedoc loading implementation starts here                      **/

/**
 * Determine if there are any sprites to load.
 */
$(document).ready( function() {
	if (loadspriteCount() !== 0) {
		getSpriteNames();
		
		for (var i = 0; i < sprites.length; i++) {
			renderSpriteDoc(sprites[i]);
		}
	}
});

/**
 * Check if there is a loadsprite element
 */
function loadspriteCount() {
	return $(i18n.loadspriteElement).get().length;
}

/**
 * This function gets the sprite names to load based on the value of loadsprite
 * element's sprite attribute.
 */
function getSpriteNames() {
	var loadspriteElements = $(i18n.loadspriteElement).get();
	
	for (var i = 0; i < loadspriteCount(); i++) {
		sprites.push(loadspriteElements[i].getAttribute(i18n.loadspriteSpriteAttribute));
	}
}

/**
 * This function is responsible for rendering the spritedoc.
 * 
 * @param sprite - sprite to load.
 */
function renderSpriteDoc(sprite) {
	var spriteModule = "{{#invoke\:" + i18n.spriteModule + "|" + i18n.spriteModuleGetRawSpriteDataFunction + "|" + sprite + "}}"; // Colon is escaped here to prevent a redlink from showing up in some category. This won't affect the script itself.
	
	$.ajax({
		type: 'GET',
		url: mw.util.wikiScript('api'),
		data: {
			action: 'expandtemplates',
			prop: 'wikitext',
			format: 'json',
			text: spriteModule,
		},
		dataType: 'json',
		success: function(response) {
			// We want to use the actual sprite data here, stored in
			// expandtemplates.wikitext
			var spriteData = response.expandtemplates.wikitext;
			
			// Strip trailing zero which gets added after parsing the wikitext.
			//  Incase it gets fixed, check if it's trailing.
			//
			// The trailing zero is intentionally checked greedily, as testing
			// proved it is *nor* a string *nor* a number
			if (spriteData.charAt(-1) == 0) {
				spriteData = spriteData.slice(0, -1);
			}
			
			// Convert sprite data to an object
			spriteData = JSON.parse(spriteData);
			
			// Create separate variable for ids, sorted alphabetically. Also
			// convert it to an array with objects
			spriteDataIds = sortKeys(spriteData.ids);
			spriteDataIds = convertObjectToArrayAndRetainKeys(spriteDataIds);
			spriteDataSections = spriteData.sections;
			spriteDataSettings = spriteData.settings;
			
			// The first function gets the URL of the spritesheet, which is
			// needed to actually render the sprites of the spritedoc.
			//
			// The second and third functions generate the spritedoc content and
			// table of contents, respectively.
			getSpritesheetUrl();
			createSpriteDocSections();
			generateTOC();
			
			$(i18n.loadspriteElement).remove();
			
			// This function checks if the spritedoc has fully loaded using the
			// variables lastSection and lastBox. They represent the last
			// section loaded and the last box of a section, respectively. If
			// both variables are true, it proceeds loading the sprite editor.
			// Else it waits 100ms before trying again.
			(function checkIfLoaded() {
				if (lastSection === true && lastBox === true) {
					spriteEditLoader();
				} else {
					setTimeout(checkIfLoaded, 100);
				}
			})();
			
		},
		error: function() {
			return mw.notify(loadingSpriteDataFailed, {autoHide: false, title: warning});
		}
	});
}

/**
 * This function is responsible for creating the sections where all images will
 * sit.
 */
function createSpriteDocSections() {
	// Wait until the variable spritesheetUrl is set. This is because we need
	// the URL to continue, else we'll end up with an invalid one and no
	// sprites.
	if (spritesheetUrl || spritesheetUrl === false) {
		for (var i = 0; i < spriteDataSections.length; i++) {
			var spriteSectionName = spriteDataSections[i].name;
			var spriteSectionId = spriteDataSections[i].id;
			
			var spriteSectionBox = createSpriteDocBoxes(spriteSectionId);
			
			var spritedocHeader = "<h3><span id='" + spriteSectionName.replace(/ /g, "_") + "' class='mw-headline'>" + spriteSectionName + "</span></h3>";
			var spritedocSection = "<div class='spritedoc-section' id='spritedocsection" + spriteSectionId + "' data-section-id='" + spriteSectionId + "'>" + spritedocHeader + spriteSectionBox + "</div>";
			
			$('#spritedoc').append(spritedocSection);
			
			if (i + 1 === spriteDataSections.length) {
				lastSection = true;
			} else {
				lastSection = false;
			}
		}
	} else {
		setTimeout(createSpriteDocSections, 100);
	}
}

/**
 * This function is responsible for creating the content (= boxes with in there
 * the sprite names) of the page.
 * 
 * @param spriteSectionId - In what section the sprites should be put,
 *							identified by a section id.
 */
function createSpriteDocBoxes(spriteSectionId) {
	var spriteDocBox = "<ul class='spritedoc-boxes'>";
	
	// Filter the sprites for the section we want
	var spritesFilteredOnSection = filterArrayWithObjects(spriteDataIds, "section", spriteSectionId);
	
	// Filter on sprite position
	var spritesFilteredOnPos = removeDuplicatesOfArrayWithObjects(spritesFilteredOnSection, "pos");
	
	spriteDocBox += createSpriteDocBoxContent(spritesFilteredOnSection, spritesFilteredOnPos);
	spriteDocBox += "</ul>";
	
	return spriteDocBox;
}

/**
 * This function lists the names of the sprites based on their pos key.
 * 
 * @param sprites - Array of sprites, unfiltered
 * @param uniqueSprites - Array of unique sprites (= without duplicate
 *						  positions, for example)
 */
function createSpriteDocBoxContent(sprites, uniqueSprites) {
	var spriteDocContent = ""; // empty string is needed to prevent 'undefined' from appearing everywhere
	
	for (var i = 0; i < uniqueSprites.length; i++) {
		var uniqueSpritePosition = uniqueSprites[i].pos;
		var filteredSpritesOnPos = filterArrayWithObjects(sprites, "pos", uniqueSpritePosition);
		
		spriteDocContent += "<li class='spritedoc-box' data-pos='" + uniqueSpritePosition + "'>";
		spriteDocContent += getSpriteFromSheet(uniqueSpritePosition);
		spriteDocContent += "<ul class='spritedoc-names'>";
		
		for (j = 0; j < filteredSpritesOnPos.length; j++) {
			spriteDocContent += "<li class='spritedoc-name'>"
				+ "<code" + (filteredSpritesOnPos[j].deprecated ? " class='spritedoc-deprecated'" : "") + ">" + filteredSpritesOnPos[j].name + "</code>"
				+ "</li>";
		}
		
		spriteDocContent += "</ul></li>";
		
		if (i + 1 === uniqueSprites.length) {
			lastBox = true;
		} else {
			lastBox = false;
		}
	}
	
	return spriteDocContent;
}

/**
 * This function creates the sprite images and needed HTML elements.
 * 
 * @param spritePos - sprite position of the wanted sprite.
 */
function getSpriteFromSheet(spritePos) {
	var backgroundImage = "background-image:url(" + (spritesheetUrl + "&version=" + currentTimestamp) + ");";
	var calculatedSpritePosition = calculateSpritePositionOnSheet(spritePos);
	var sprite = "<div class='spritedoc-image'>"
		+ "<span class='sprite " + spriteDataSettings.name.toLowerCase() + "-sprite' style='" + backgroundImage + calculatedSpritePosition + "'>"
		+ "<br>"
		+ "</span>"
		+ "</div>";
	
	return sprite;
}

/**
 * This function generates a table of contents above the sprite boxes.
 * 
 * Internally, the function uses the global variable spriteDataSections to get
 * the names of the sections and generates the table of contents based upon
 * that.
 */
function generateTOC() {
	var language = mw.config.get('wgUserLanguage');
	var TOCText = i18n.TOCText;
	
	var toc = "<div id='spritetoc' class='toc' style='float:right'>"
		+ "<input id='spritetoctogglecheckbox' class='toctogglecheckbox' type='checkbox' role='button' style='display:none'>"
		+ "<div class='toctitle' lang='" + language + "'>"
		+ "<h2>" + TOCText + "</h2>"
		+ "<span class='toctogglespan'>"
		+ "<label class='toctogglelabel' for='spritetoctogglecheckbox'></label>"
		+ "</span>"
		+ "</div>"
		+ "<ul>";
	
	for (var i = 0; i < spriteDataSections.length; i++) {
		var spriteSectionName = spriteDataSections[i].name;
		
		toc += "<li class='toclevel-1 tocsection-" + (i + 1) + "'>\xA0"
			+ "<a href='#" + spriteSectionName.replace(/ /g, "_") + "'>"
			+ "<span class='tocnumber'>" + (i + 1) + "</span>"
			+ "<span class='toctext'>" + spriteSectionName + "</span>"
			+ "</a>"
			+ "</li>";
	}
	
	toc += "</ul>"
		+ "</div>";
	
	$('#spritedoc').prepend(toc);
}

/** Sprite editor loading implementation starts here.                        **/
/** @author Majr                                                             **/

/**
 * Add an edit button which loads the sprite editor
 *
 * If spriteaction=edit is in the URL, the editor will be loaded
 * immediately, otherwise it will wait for the button to be clicked.
 */
function spriteEditLoader() {
	var editPage = $('#sprite-editor-message').data('page') || null;
	
	if (!$('#spritedoc').length && !editPage) {
		
		return;
	}
	
	var $editTab = $('#ca-edit');
	
	if (!$editTab.length) {
		$editTab = $('#ca-viewsource');
	}
	
	var $spriteEditLink = $('<a>').text('Sprite bewerken').attr('href',
		mw.util.getUrl(editPage, {spriteaction: 'edit'})
	);
	
	var $spriteEditTab = $('<li>').attr('id', 'ca-spriteedit').append(
		$('<span>').append($spriteEditLink)
	);
	
	$spriteEditTab.insertAfter($editTab);
	
	// Page to sprite edit is not here, so no need to bind events
	if (editPage) {
		return;
	}
	
	var loadSpriteEditor = function() {
		$spriteEditTab.add('#ca-view').toggleClass('selected');
		
		return mw.loader.using('ext.gadget.spriteEdit');
	};
	
	if ( location.search.match('[?&]spriteaction=edit') ) {
		loadSpriteEditor();
		
		return;
	}
	
	var $win = $(window);
	
	$spriteEditLink.one('click.spriteEditLoader', function(e) {
		// Initially add the history so it is not delayed waiting
		// for the editor to load. The editor will handle it from now.
		history.pushState({}, '', this.href);
		
		loadSpriteEditor().then(function() {
			$win.off('.spriteEditLoader');
		});
		
		e.preventDefault();
	});
	
	// If the page is reloaded while the editor isn't loaded, navigating
	// back to the editor won't work, so an initial navigation check is
	// necessary to load the editor, where it will then monitor navigation
	$win.on('popstate.spriteEditLoader', function() {
		if (
			location.search.match('[?&]spriteaction=edit') &&
			!$('html').hasClass('spriteedit-loaded')
		) {
			loadSpriteEditor().then( function() {
				$win.off('.spriteEditLoader');
			});
		}
	});
}

/** Helper functions                                                         **/

/**
 * This function sorts an object based on what it's passed.
 * 
 * @param unsortedKeys - object with unsorted keys.
 */
function sortKeys(unsortedKeys) {
	var orderedKeys = {};
	
	Object.keys(unsortedKeys).sort().forEach(function(key) {
		orderedKeys[key] = unsortedKeys[key];
	});
	
	return orderedKeys;
}

/**
 * This function converts an object into an array, while retaining all keys as
 * well. In this case, the key is repositioned in the object and named 'name'.
 * 
 * @param convertableObject - Object to convert to an array.
 */
function convertObjectToArrayAndRetainKeys(convertableObject) {
	var convertedArray = [];
	
	for (var key in convertableObject) {
		convertedArray.push(Object.assign(convertableObject[key], {name: key}));
	}
	
	return convertedArray;
}

/**
 * This function filters an array with objects, based on what it's given.
 * 
 * @param unfilteredArray - the unfiltered array.
 * @param filterOn - string of the key of the object it needs to filter on.
 * @param valueToFilterOn - requested value where it needs to filter on
 */
function filterArrayWithObjects(unfilteredArray, filterOn, valueToFilterOn) {
	var filteredArray = unfilteredArray.filter(function(element) {
		return element[filterOn] === valueToFilterOn;
	});
	
	return filteredArray;
}

/**
 * This function removes duplicates of an array with objects, based on what is
 * passed to it.
 * 
 * @param arrayWithDuplicates - array with objects with (possible) duplicate
 *								values.
 * @param filterOn - string of the key of the object where it needs to filter
 *					 on.
 */
function removeDuplicatesOfArrayWithObjects(arrayWithDuplicates, filterOn) {
	var distinct = [];
	
	/* NOTE: There ought to be a better way to do this, but for now it's fine. */
	for (var i = 0; i < arrayWithDuplicates.length; i++) {
		var duplicate;
		
		// Check if the to be filtered value is already in the distinct array
		for (var j = 0; j < distinct.length; j++) {
			if (arrayWithDuplicates[i][filterOn] === distinct[j][filterOn]) {
				// if it is, set duplicate to true and break the for loop
				duplicate = true;
				
				break;
			} else {
				// else set this on false
				duplicate = false;
			}
		}
		
		// if it isn't a duplicate, add it to the distinct array
		if (!duplicate) {
			distinct.push(arrayWithDuplicates[i]);
		}
	}
	
	return distinct;
}

/**
 * This function gets the image of the sprite based on the
 * spriteDataSettings.image key value. It depends on the FileUrl template, with
 * as name i18n.fileUrlTemplate and as single argument the requested image.
 */
function getSpritesheetUrl() {
	var image = spriteDataSettings.image ? spriteDataSettings.image : (spriteDataSettings.name + i18n.spriteSuffix );
	var fileUrlText = "\{\{" + i18n.fileUrlTemplate + "|" + image + "\}\}"; // The curly brackets are escaped to prevent a redlink from appearing
	
	$.ajax({
		type: 'GET',
		url: mw.util.wikiScript('api'),
		data: {
			action: 'parse',
			prop: 'text',
			format: 'json',
			text: fileUrlText,
			contentmodel: 'wikitext',
			disablelimitreport: true,
		},
		dataType: 'json',
		success: function(response) {
			var spritesheetUrlArray = response.parse.text["*"].match(/url\((.*?)\)/);
			
			return spritesheetUrl = spritesheetUrlArray[1];
		},
		error: function() {
			mw.notify(loadingSpriteImageFailed, {autoHide: false, title: warning});
			
			return spritesheetUrl = false;
		}
	});
}

/**
 * This function calculates the X and Y of the sprite on the spritesheet, based
 * on the sprite position it's given. This function is based upon the p.base
 * function in the sprite module, minus additional functionality like built-in
 * link support and the like.
 * 
 * @param spritePos - the sprite's position on the sheet.
 */
function calculateSpritePositionOnSheet(spritePos) {
	var pos;
	var size = spriteDataSettings.size ? spriteDataSettings.size : spriteDefaults.size;
	var sheetWidth = spriteDataSettings.sheetsize ? spriteDataSettings.sheetsize : spriteDefaults.sheetsize;
	var tiles = sheetWidth / size;
	var scale = spriteDataSettings.scale ? spriteDataSettings.scale : spriteDefaults.scale;
	var autoScale = spriteDataSettings.autoscale ? spriteDataSettings.autoscale : spriteDefaults.autoscale;
	
	var spritePositionAndCSS;
	
	if (spritePos) {
		pos = spritePos - 1;
	} else if (spriteDataSettings.pos) {
		pos = spriteDataSettings.pos - 1;
	} else {
		pos = spriteDefaults.pos - 1;
	}
	
	var left = pos % tiles * size * scale;
	var top = Math.floor(pos / tiles) * size * scale;
	
	spritePositionAndCSS = "background-position:-" + left + "px -" + top + "px;";
	
	if (autoScale && scale !== spriteDefaults.scale) {
		spritePositionAndCSS += "background-size:" + (sheetWidth * scale) + "px auto;";
	}
	
	if (size !== spriteDefaults.size || (autoScale && scale !== spriteDefaults.scale)) {
		spritePositionAndCSS += "height:" + (size * scale) + "px;";
		spritePositionAndCSS += "width:" + (size * scale) + "px;";
	}
	
	return spritePositionAndCSS;
}
Advertisement