<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wiki.runerealm.org/index.php?action=history&amp;feed=atom&amp;title=MediaWiki%3AGadget-musicMap-core.js</id>
	<title>MediaWiki:Gadget-musicMap-core.js - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.runerealm.org/index.php?action=history&amp;feed=atom&amp;title=MediaWiki%3AGadget-musicMap-core.js"/>
	<link rel="alternate" type="text/html" href="https://wiki.runerealm.org/index.php?title=MediaWiki:Gadget-musicMap-core.js&amp;action=history"/>
	<updated>2026-04-11T05:10:07Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.41.1</generator>
	<entry>
		<id>https://wiki.runerealm.org/index.php?title=MediaWiki:Gadget-musicMap-core.js&amp;diff=42204&amp;oldid=prev</id>
		<title>Alex at 11:06, 20 October 2024</title>
		<link rel="alternate" type="text/html" href="https://wiki.runerealm.org/index.php?title=MediaWiki:Gadget-musicMap-core.js&amp;diff=42204&amp;oldid=prev"/>
		<updated>2024-10-20T11:06:17Z</updated>

		<summary type="html">&lt;p&gt;&lt;/p&gt;
&lt;a href=&quot;https://wiki.runerealm.org/index.php?title=MediaWiki:Gadget-musicMap-core.js&amp;amp;diff=42204&amp;amp;oldid=891&quot;&gt;Show changes&lt;/a&gt;</summary>
		<author><name>Alex</name></author>
	</entry>
	<entry>
		<id>https://wiki.runerealm.org/index.php?title=MediaWiki:Gadget-musicMap-core.js&amp;diff=891&amp;oldid=prev</id>
		<title>Alex: Created page with &quot;/* Music map  * Generates an interactive music map that has toggleable polygons on it. Can be used as a &#039;checklist&#039; to track music track unlock progression.  * See Map:Music tracks  */  var MM = {}; MM.touch = false; MM.getUnlocked = function() { 	var ls = localStorage.getItem(&#039;musicMap-&#039;+mw.config.get(&#039;wgPageName&#039;)); 	if (!ls) return []; 	// map characters back to numbers and convert to  	var bitstr = Array.prototype.map.call(ls, function(x) { // go through each cha...&quot;</title>
		<link rel="alternate" type="text/html" href="https://wiki.runerealm.org/index.php?title=MediaWiki:Gadget-musicMap-core.js&amp;diff=891&amp;oldid=prev"/>
		<updated>2024-10-13T00:51:08Z</updated>

		<summary type="html">&lt;p&gt;Created page with &amp;quot;&lt;span dir=&quot;auto&quot;&gt;&lt;span class=&quot;autocomment&quot;&gt;Music map  * Generates an interactive music map that has toggleable polygons on it. Can be used as a &amp;#039;checklist&amp;#039; to track music track unlock progression.  * See &lt;a href=&quot;/index.php?title=Map:Music_tracks&amp;amp;action=edit&amp;amp;redlink=1&quot; class=&quot;new&quot; title=&quot;Map:Music tracks (page does not exist)&quot;&gt;Map:Music tracks&lt;/a&gt;: &lt;/span&gt;  var MM = {}; MM.touch = false; MM.getUnlocked = function() { 	var ls = localStorage.getItem(&amp;#039;musicMap-&amp;#039;+mw.config.get(&amp;#039;wgPageName&amp;#039;)); 	if (!ls) return []; 	// map characters back to numbers and convert to  	var bitstr = Array.prototype.map.call(ls, function(x) { // go through each cha...&amp;quot;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;/* Music map&lt;br /&gt;
 * Generates an interactive music map that has toggleable polygons on it. Can be used as a &amp;#039;checklist&amp;#039; to track music track unlock progression.&lt;br /&gt;
 * See [[Map:Music tracks]]&lt;br /&gt;
 */&lt;br /&gt;
&lt;br /&gt;
var MM = {};&lt;br /&gt;
MM.touch = false;&lt;br /&gt;
MM.getUnlocked = function() {&lt;br /&gt;
	var ls = localStorage.getItem(&amp;#039;musicMap-&amp;#039;+mw.config.get(&amp;#039;wgPageName&amp;#039;));&lt;br /&gt;
	if (!ls) return [];&lt;br /&gt;
	// map characters back to numbers and convert to &lt;br /&gt;
	var bitstr = Array.prototype.map.call(ls, function(x) { // go through each character in the string&lt;br /&gt;
		var str = &amp;#039;00000&amp;#039; + parseInt(x, 32).toString(2); // parse back to bit string with sufficient leading zeroes to turn it into 5 bits long&lt;br /&gt;
		return str.slice(-5); // the actual bits that were parsed, plus any leading zeroes if needed&lt;br /&gt;
	}).join(&amp;#039;&amp;#039;); // convert array of bitstrings to single long strong&lt;br /&gt;
	// convert bitstring back into an array of bools&lt;br /&gt;
	var bits = Array.prototype.map.call(bitstr, function(x) { return parseInt(x); });&lt;br /&gt;
	return bits;&lt;br /&gt;
}&lt;br /&gt;
MM.saveUnlocked = function(arr) {&lt;br /&gt;
	var bits = [];&lt;br /&gt;
	for (var i=0; i&amp;lt;arr.length; i++) bits[i] = arr[i] ? 1 : 0; // fill in empty array elements&lt;br /&gt;
	var bitstr = bits.join(&amp;#039;&amp;#039;); // array in bit representation&lt;br /&gt;
	// Split up in chunks of 5 bits. The array length should be a multiple of 5 based on the musicMap function.&lt;br /&gt;
	// Use 32-bit string, because toString(64) is not supported in plain JS.&lt;br /&gt;
	var b32 = bitstr.match(/.{1,5}/g).map(function(x) { return parseInt(x, 2).toString(32); });&lt;br /&gt;
	localStorage.setItem(&amp;#039;musicMap-&amp;#039;+mw.config.get(&amp;#039;wgPageName&amp;#039;), b32.join(&amp;#039;&amp;#039;));&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
MM.arrIdx = function(arr, i) {&lt;br /&gt;
	if (i &amp;lt; 0) {&lt;br /&gt;
		return arr[arr.length + i];&lt;br /&gt;
	} else {&lt;br /&gt;
		return arr[i];&lt;br /&gt;
	}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
MM.toggleTrack = function(track, ids, state, $targets) {&lt;br /&gt;
	if ($targets) {&lt;br /&gt;
		$targets = $targets.add(&amp;#039;.mw-kartographer-interactive&amp;#039;);&lt;br /&gt;
	} else {&lt;br /&gt;
		$targets = $(&amp;#039;.mw-kartographer-interactive&amp;#039;);&lt;br /&gt;
	}&lt;br /&gt;
	if (state == undefined) {&lt;br /&gt;
    	state = MM.arrIdx(unlockedTracks, track) ? 0 : 1;&lt;br /&gt;
	}&lt;br /&gt;
	// update all maps when one map is clicked&lt;br /&gt;
	$targets.each(function() {&lt;br /&gt;
		for (var i in ids) {&lt;br /&gt;
	        var el = $(this).find(&amp;#039;path.leaflet-interactive&amp;#039;).eq(ids[i]);&lt;br /&gt;
	        if (state) el.addClass(&amp;#039;unlocked&amp;#039;); else el.removeClass(&amp;#039;unlocked&amp;#039;);&lt;br /&gt;
		}&lt;br /&gt;
	});&lt;br /&gt;
    if (window.unlockedTracks) {&lt;br /&gt;
	    unlockedTracks[track] = state;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
	&lt;br /&gt;
MM.unlockTrack = function(e) {&lt;br /&gt;
	if (!e.ctrlKey &amp;amp;&amp;amp; !e.metaKey &amp;amp;&amp;amp; !(MM.touch &amp;amp;&amp;amp; e.type == &amp;#039;selectstart&amp;#039;)) {&lt;br /&gt;
		// not ctrl+click, AND not cmd+click, AND not a long press touch event&lt;br /&gt;
		return; // neither ctrl+click nor longpress&lt;br /&gt;
	}&lt;br /&gt;
	var map = $(e.target).closest(&amp;#039;.mw-kartographer-interactive&amp;#039;).data(&amp;#039;musicMap&amp;#039;);&lt;br /&gt;
	var i = $(e.target).index();&lt;br /&gt;
	var el = $(&amp;#039;#musicMap [value~=&amp;quot;&amp;#039;+i+&amp;#039;&amp;quot;]&amp;#039;);&lt;br /&gt;
	MM.toggleTrack(parseInt(el.html()), el.val().split(&amp;#039; &amp;#039;).map(Number));&lt;br /&gt;
	MM.saveUnlocked(unlockedTracks);&lt;br /&gt;
	map.closePopup(); // close popups on current map if there were any that were open.&lt;br /&gt;
	e.preventDefault();&lt;br /&gt;
	e.stopPropagation();&lt;br /&gt;
	return false;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
MM.unlockAll = function(state) {&lt;br /&gt;
	var btn = this;&lt;br /&gt;
	btn.setDisabled(true);&lt;br /&gt;
	// doing the track toggles ensures the button gets disabled properly before rendering the other DOM changes&lt;br /&gt;
	setTimeout(function() {&lt;br /&gt;
		$(&amp;#039;#musicMap data&amp;#039;).each(function() {&lt;br /&gt;
			var track = parseInt(this.innerHTML);&lt;br /&gt;
			var ids = this.value.split(&amp;#039; &amp;#039;).map(Number);&lt;br /&gt;
			MM.toggleTrack(track, ids, state ? 1 : 0);&lt;br /&gt;
		});&lt;br /&gt;
		MM.saveUnlocked(unlockedTracks); // save once at the end&lt;br /&gt;
	}, 1);&lt;br /&gt;
	setTimeout(function() { // prevent doubleclicking the button: disable for 3 seconds&lt;br /&gt;
		btn.setDisabled(false);&lt;br /&gt;
	}, 3000);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
MM.musicMap = function(map) {&lt;br /&gt;
	if ($(&amp;#039;#musicMap&amp;#039;).length == 0) return;&lt;br /&gt;
	$target = $(map._container);&lt;br /&gt;
	if ($target.data(&amp;#039;musicMap&amp;#039;)) return; // already added event handlers&lt;br /&gt;
	$target.data(&amp;#039;musicMap&amp;#039;, map);&lt;br /&gt;
&lt;br /&gt;
	/* Local storage format:&lt;br /&gt;
	 * base32-encoded string&lt;br /&gt;
	 * All songs with an associated cache ID will be in the array at that position&lt;br /&gt;
	 * A gap to make this array&amp;#039;s total length a multiple of 5 bits (since 2^5 = 32)&lt;br /&gt;
	 * A gap of 20 to prevent newly released songs from being marked as unlocked&lt;br /&gt;
	 * All N songs without a cache ID will be placed at the end, alphabetically sorted:&lt;br /&gt;
	 *  with [length-1] being a, and [length-N] being z.&lt;br /&gt;
	 */&lt;br /&gt;
	var ls = MM.getUnlocked();&lt;br /&gt;
	var unlocked = [],&lt;br /&gt;
		idless = [];&lt;br /&gt;
	$(&amp;#039;#musicMap data&amp;#039;).each(function() {&lt;br /&gt;
		// rebuild local storage data based on the &amp;lt;data&amp;gt;, because the track list might have changed.&lt;br /&gt;
		var track = parseInt(this.innerHTML);&lt;br /&gt;
		var ids = this.value.split(&amp;#039; &amp;#039;).map(Number);&lt;br /&gt;
		if (MM.arrIdx(ls, track)) {&lt;br /&gt;
			MM.toggleTrack(track, ids, 1, $target);&lt;br /&gt;
		}&lt;br /&gt;
		if (track &amp;gt;= 0) {&lt;br /&gt;
			unlocked[track] = MM.arrIdx(ls, track) ? 1 : 0;&lt;br /&gt;
		} else {&lt;br /&gt;
			idless[-track - 1] = MM.arrIdx(ls, track) ? 1 : 0;&lt;br /&gt;
		}&lt;br /&gt;
	});&lt;br /&gt;
	// gap of 5-(lengths%5) to make unlocked part a multiple of 5 bits (for base32enc). 20 empty slots as a spacer.&lt;br /&gt;
	window.unlockedTracks = unlocked.concat(Array(5-((unlocked.length+idless.length) % 5) + 20)).concat(idless);&lt;br /&gt;
	MM.saveUnlocked(unlockedTracks);&lt;br /&gt;
	&lt;br /&gt;
	$target.find(&amp;#039;path&amp;#039;).click(MM.unlockTrack).dblclick(function(e) {&lt;br /&gt;
		if (e.ctrlKey || e.metaKey) {&lt;br /&gt;
			// ctrl+dblclick already gets handled by the click handler; don&amp;#039;t fullscreen etc.&lt;br /&gt;
			e.preventDefault();&lt;br /&gt;
			e.stopPropagation();&lt;br /&gt;
		}&lt;br /&gt;
	}).on(&amp;#039;touchstart&amp;#039;, function(e) { // Handle long-press touch events to unlock tracks: https://stackoverflow.com/q/66546226/1256925&lt;br /&gt;
		MM.touch = true;&lt;br /&gt;
	}).on(&amp;#039;touchend&amp;#039;, function(e) {&lt;br /&gt;
		MM.touch = false;&lt;br /&gt;
	}).on(&amp;#039;selectstart&amp;#039;, MM.unlockTrack);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
MM.playTrack = function(e) {&lt;br /&gt;
	// This handler will trigger before the audioplayer.js event handler, because&lt;br /&gt;
	// this handler is tied to the map container, and that handler is tied to body.&lt;br /&gt;
	e.preventDefault();&lt;br /&gt;
	var $clone = $(e.target).clone(); // make a copy to put back in the tooltip&lt;br /&gt;
	var parent = e.target.parentElement; // where to insert the copy&lt;br /&gt;
	$(&amp;#039;#music-playlist .player&amp;#039;).html(&amp;#039;&amp;#039;); // remove previous player&lt;br /&gt;
	$(e.target).appendTo(&amp;#039;#music-playlist .player&amp;#039;); // move the song that will play to the play-box; audioplayer.js will replace this with &amp;lt;audio&amp;gt;.&lt;br /&gt;
	$clone.appendTo(parent); // put the link back in the tooltip&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
MM.initMap = function(map, fullscreen) {&lt;br /&gt;
	if (!$(&amp;#039;#musicMap&amp;#039;).length) return;&lt;br /&gt;
	if (map instanceof Array) map = map[0];&lt;br /&gt;
	// wait for this map to be ready and make it a musicmap&lt;br /&gt;
	map.on(&amp;#039;kartographerisready&amp;#039;, function() { MM.musicMap(this); });&lt;br /&gt;
	if (fullscreen === false) {&lt;br /&gt;
		// make the fullscreen maps that may be created also turn into musicmaps&lt;br /&gt;
		L.Map.addInitHook(function() {MM.initMap(this, true);})&lt;br /&gt;
	}&lt;br /&gt;
	if ($(&amp;#039;#music-playlist .player&amp;#039;).length == 0) {&lt;br /&gt;
		$(&amp;#039;#music-playlist&amp;#039;).show().append(&amp;#039;&amp;lt;div class=&amp;quot;player&amp;quot;&amp;gt;Click a link in a map tooltip to play that track.&amp;lt;/div&amp;gt;&amp;#039;);&lt;br /&gt;
		$(map._container).on(&amp;#039;click&amp;#039;, &amp;#039;a[href^=&amp;quot;/w/File:&amp;quot;][href$=&amp;quot;.ogg&amp;quot;]&amp;#039;, MM.playTrack)&lt;br /&gt;
		$(map._container).on(&amp;#039;click&amp;#039;, &amp;#039;a:not([href^=&amp;quot;/w/File:&amp;quot;][href$=&amp;quot;.ogg&amp;quot;])&amp;#039;, function() {&lt;br /&gt;
			this.target = &amp;#039;_blank&amp;#039;; // open song links in new tab to prevent having to reload the map&lt;br /&gt;
		});&lt;br /&gt;
	}&lt;br /&gt;
	if ($(&amp;#039;.musicMap-buttons&amp;#039;).length == 0) {&lt;br /&gt;
		var unlockbtn = new OO.ui.ButtonWidget( {&lt;br /&gt;
			flags: [ &amp;#039;progressive&amp;#039; ],&lt;br /&gt;
			label: &amp;#039;Unlock all tracks&amp;#039;,&lt;br /&gt;
		} );&lt;br /&gt;
		unlockbtn.on(&amp;#039;click&amp;#039;, MM.unlockAll.bind(unlockbtn, 1));&lt;br /&gt;
		var lockbtn = new OO.ui.ButtonWidget( {&lt;br /&gt;
			flags: [ &amp;#039;destructive&amp;#039; ],&lt;br /&gt;
			label: &amp;#039;Lock all tracks&amp;#039;,&lt;br /&gt;
		} );&lt;br /&gt;
		lockbtn.on(&amp;#039;click&amp;#039;, MM.unlockAll.bind(lockbtn, 0));&lt;br /&gt;
		// place in a wrapper and add to body&lt;br /&gt;
		$(&amp;#039;&amp;lt;div&amp;gt;&amp;#039;).addClass(&amp;#039;musicMap-buttons&amp;#039;).append(unlockbtn.$element, lockbtn.$element).appendTo(&amp;#039;#musicMap-info&amp;#039;);&lt;br /&gt;
	}&lt;br /&gt;
	return;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// hook init to maps loading&lt;br /&gt;
mw.hook(&amp;#039;wikipage.maps&amp;#039;).add(MM.initMap);&lt;/div&gt;</summary>
		<author><name>Alex</name></author>
	</entry>
</feed>