MediaWiki:Common.js

From Galaxy Life Wiki
Jump to navigation Jump to search

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.
/*
const osReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
let reducedMotionMode = osReducedMotion ? true : false;

mediaQuery.addEventListener('change', function() {
	console.log(mediaQuery.media, mediaQuery.matches);
});
*/

var modals = {};

// (Kinda) preventing a bug where the top buttons would collapse and un-collapse
// indefinetly by moving the history button into the dropdown menu.
function moveHistoryToDropdown() {
	function buttonToDropdown(target) {
		const dropdownMenu = document.querySelector('#p-cactions .vector-menu-content-list');
	
		if (!target) throw new Error('Target element is missing.');
		else if (!dropdownMenu) return;
	
		dropdownMenu.prepend(target);
	}

	const historyButton = document.getElementById('ca-history');

	buttonToDropdown(historyButton);
}

// Prevent tables from extending beyond the page's width.
function tableOverflowInit() {
	const tables = document.querySelectorAll('.mw-parser-output table'),
		overflowWrappers = document.querySelectorAll('.overflow-wrapper');

	if (tables.length === 0 || overflowWrappers.length > 0) return;

	tables.forEach(function(table) {
		const overflowWrapper = document.createElement('div');

		overflowWrapper.className = 'overflow-wrapper';

		table.parentNode.insertBefore(overflowWrapper, table);
		overflowWrapper.appendChild(table);
	});
}

// Make elements appear when scrolling to them.
function animateOnView() {
	'use strict';

	// Here you put all the elements you want to show on scroll.
	const animatedElements = document.querySelectorAll('.is-invisible'),
		eventsSection = document.body;

		if (!animatedElements.length) return;

	// Each element mentioned above will execute the "fade on scroll" script.
	animatedElements.forEach(function(elem) {
		const canAnimate = !elem.classList.contains('has-animated');

		if (canAnimate) attachViewportObserver(elem);
	});

	function attachViewportObserver(elem) {
		// Here you can configure how the observer will work.
		const observerConfig = {
			// Use the viewport as the observer.
			root: null,
			// Increases bounding box of the observer to be half of the screen. This means
			// that elements won't fade into view until their topmost pixel reaches the
			// window's half height.
			rootMargin: '0px 0px -20% 0px',
			// Activate it when 0% of the element intersects with the observer.
			threshold: 0.0
		};

		const observer = new IntersectionObserver(function(e) {
			e.forEach(function (e) {
				// If the element is on view, trigger a function that makes it visible.
				e.isIntersecting && handleIntersection();
			});
		}, observerConfig);

		// Observes the elements declared on line 3.
		observer.observe(elem);

		// Adds the 'is-visible' class which, with CSS, will change the element's
		// opacity from "0" to "1" and will move it 16px up by updating
		// "transform: translateY(16px)" to "transform: translateY(0)".
		// It also stops observing the element.
		function handleIntersection() {
			const delayAttribute = elem.getAttribute('data-animation-delay'),
				delay = isNaN(delayAttribute) ? 0 : delayAttribute;

			setTimeout(function() {
				elem.classList.remove('is-invisible');
				elem.classList.add('is-visible', 'has-animated');
				observer.disconnect();
			}, delay);
		}

		/*
		function unhandleIntersection() {
			elem.classList.remove('is-visible');
			// observer.disconnect();
		}
		*/
	}

	/*
	function showEventContents() {
		// ...
	}

	eventsSection.addEventListener('transitionend', showEventContents);
	*/
}

// Initialize news popup on the main page.
function newsInit() {
	'use strict';

	const newsContainer = document.getElementById('mp-news');

	if (!newsContainer) return;

	const username = mw.config.get('wgUserName'),
		classes = {
			panel: 'announcement-panel',
			head: 'announcement-head',
			title: 'announcement-title',
			body: 'announcement-body',
			image: 'announcement-image',
			description: 'announcement-description',
			footer: 'announcement-footer',
			pagination: 'announcement-pagination',
			paginationText: 'announcement-pagination-text',
			buttonFirst: 'button-first',
			buttonPrev: 'button-prev',
			buttonNext: 'button-next',
			buttonLast: 'button-last',
			ctaContainer: 'announcement-cta-container',
			cta: 'announcement-cta'
		},
		newsData = [
			{
				title: 'Christmas event',
				date: '25/12/2023',
				image: {
					src: 'https://galaxylife.wiki.gg/images/f/fd/Winter_1.png',
					alt: 'Sparragon with a Santa Claus hat flying with his S-Trike with gifts over some Starlings during a snowy night',
					height: 245,
					width: 518
				},
				description: 'Defeat Sparragon\'s forces and recover the gifts he stole to save Christmas!',
				cta: {
					href: 'https://galaxylife.wiki.gg/wiki/Christmas_Event',
					label: null
				}
			},
			{
				title: 'Halloween event',
				date: '31/10/2023',
				image: {
					src: 'https://galaxylife.wiki.gg/images/4/42/Halloween_2.png',
					alt: 'A cyan commet falling on the horizon of the main planet at dusk',
					height: 245,
					width: 518
				},
				description: 'A special event where powerful undead foes will attack your base.',
				cta: {
					href: 'https://galaxylife.wiki.gg/wiki/Halloween_Event',
					label: null
				}
			},
			{
				title: 'Doomsday',
				date: '30/10/2023',
				image: {
					src: 'https://galaxylife.wiki.gg/images/f/fc/Doomsday_3.png',
					alt: 'A Rock Golem coming out of an ancient Warp Gate',
					height: 180,
					width: 610
				},
				description: 'An event where powerful mayan Golems will emerge from a Warp Gate to wipe out your planet.',
				cta: {
					href: 'https://galaxylife.wiki.gg/wiki/Doomsday_Event',
					label: null
				}
			}
		],
		panelHTML = '\
			<div class="' + classes.panel + '">\
				<div class="' + classes.head + '">\
					<h2 class="' + classes.title + '"></h2>\
				</div>\
				<div class="' + classes.body + '">\
					<img class="' + classes.image + '"/>\
					<p class="' + classes.description + '"></p>\
				</div>\
				<div class="' + classes.footer + '">\
					<div class="' + classes.pagination + '">\
						<button class="' + classes.buttonFirst + '"></button>\
						<button class="' + classes.buttonPrev + '"></button>\
						<div class="' + classes.paginationText + '"></div>\
						<button class="' + classes.buttonNext + '"></button>\
						<button class="' + classes.buttonLast + '"></button>\
					</div>\
					<div class="' + classes.ctaContainer + '">\
						<a class="' + classes.cta + '"></a>\
					</div>\
				</div>\
			</div>';

	const minPage = 0,
		maxPage = newsData.length - 1;
	var page = minPage;

	function loadPanel(pagesData, actionType) {
		if (isNaN(page)) throw new Error('Page number should be a number.');

		const title = document.querySelector('.' + classes.title),
			image = document.querySelector('.' + classes.image),
			description = document.querySelector('.' + classes.description),
			pagination = document.querySelector('.' + classes.paginationText),
			cta = document.querySelector('.' + classes.cta);

		if (actionType === 'prev' && page === minPage || actionType === 'next' && page === maxPage) return;

		switch (actionType) {
			case 'prev':
				page--;
				break;
			case 'next':
				page++;
				break;
			case 'first':
				page = minPage;
				break;
			case 'last':
				page = maxPage;
				break;
		}

		title.innerText = pagesData[page].title;
		description.innerText = pagesData[page].description;
		pagination.innerText = String(page + 1) + '/' + String(maxPage + 1);
		setAttributes(image, {
			'src': pagesData[page].image.src,
			'alt': pagesData[page].image.alt,
			'height': pagesData[page].image.height,
			'width': pagesData[page].image.width
		});

		if (pagesData[page].cta.href) {
			cta.removeAttribute('disabled');
			cta.setAttribute('href', pagesData[page].cta.href);
		} else {
			cta.removeAttribute('href');
			cta.setAttribute('disabled', '');
		}

		cta.innerText = pagesData[page].cta.label || 'See more';

		buttonsInit();
	}

	function buttonsInit() {
		var buttonFirst = document.querySelector('.' + classes.buttonFirst),
			buttonPrev = document.querySelector('.' + classes.buttonPrev),
			buttonNext = document.querySelector('.' + classes.buttonNext),
			buttonLast = document.querySelector('.' + classes.buttonLast);
		const buttons = [buttonFirst, buttonPrev, buttonNext, buttonLast],
			isOnlyPage = minPage === maxPage,
			isFirstPage = page === minPage,
			isLastPage = page === maxPage;

		// Remove event listeners from buttons.
		buttons.forEach(function(button) {
			button.replaceWith(button.cloneNode(true));
		});

		// Select the cloned buttons (that don't have any event listeners attached).
		var buttonFirst = document.querySelector('.' + classes.buttonFirst),
			buttonPrev = document.querySelector('.' + classes.buttonPrev),
			buttonNext = document.querySelector('.' + classes.buttonNext),
			buttonLast = document.querySelector('.' + classes.buttonLast);

		// Now disable buttons depending on which page the user is.
		if (isOnlyPage) {
			setAsInactive([buttonFirst, buttonPrev, buttonNext, buttonLast]);
		} else if (isFirstPage) {
			setAsInactive([buttonFirst, buttonPrev]);
			setAsActive([buttonNext, buttonLast]);
		} else if (isLastPage) {
			setAsActive([buttonFirst, buttonPrev]);
			setAsInactive([buttonNext, buttonLast]);
		} else {
			setAsActive([buttonFirst, buttonPrev, buttonNext, buttonLast]);
		}

		// Add event listeners to non-disabled buttons.
		if (!buttonFirst.hasAttribute('disabled')) buttonFirst.addEventListener('click', function() { loadPanel(newsData, 'first'); });
		if (!buttonPrev.hasAttribute('disabled')) buttonPrev.addEventListener('click', function() { loadPanel(newsData, 'prev'); });
		if (!buttonNext.hasAttribute('disabled')) buttonNext.addEventListener('click', function() { loadPanel(newsData, 'next'); });
		if (!buttonLast.hasAttribute('disabled')) buttonLast.addEventListener('click', function() { loadPanel(newsData, 'last'); });
	}

	function setAsActive(targets) {
		targets.forEach(function(target) {
			target.removeAttribute('disabled');
			target.classList.add(classes.interactive);
		});
	}

	function setAsInactive(targets) {
		targets.forEach(function(target) {
			target.setAttribute('disabled', '');
			target.classList.remove(classes.interactive);
		});
	}

	function setAttributes(target, attrs) {
		for(var key in attrs) {
			target.setAttribute(key, attrs[key]);
		}
	}

	newsContainer.insertAdjacentHTML('beforeend', panelHTML);
	loadPanel(newsData, 'none');
}

// Add cost calculators for colonies in it's respective article.
function colonyCostsInit() {
	// Declaring useful stuff that will be used across the script.
	const costsCalculator = document.getElementById('costs-calculator'),
		fullCostListContainer = document.querySelector('.full-costs-list-generator-container'),
		costGeneratorContainer = document.querySelector('.cost-generator-container'),
		abs = Math.abs,
		max = Math.max,
		min = Math.min,
		ceil = Math.ceil,
		floor = Math.floor,
		sqrt = Math.sqrt,
		pow = Math.pow;

	// Initializing calculators.
	fullCostListInit();
	costGeneratorInit();
	costCalculatorInit();

	// List generator - this script takes two numbers from input fields and
	// generates a list whose length is based on the difference between both
	// inputs. 
	function fullCostListInit() {
		// Generating and adding html to the DOM.
		const generatorHTML = '\
		<form class="full-costs-list-generator">\
			<label for="costlist-start">Get distance costs from: </label>\
			<input type="number" id="costlist-start" name="costlist-start" min="0" max="2000" value="0" placeholder="0" required></input>\
			<label for="costlist-end">to: </label>\
			<input type="number" id="costlist-end" name="costlist-end" min="0" max="2000" value="10" placeholder="10" required></input>\
			<input type="submit" class="wds-button full-costs-list-generator-submit" value="Get costs"></input>\
		</form>\
		<p class="full-cost-generator-output">Costs: </p>\
		<ul class="full-costs-list"></ul>';

		fullCostListContainer.insertAdjacentHTML('beforeend', generatorHTML);

		const generator = document.querySelector('.full-costs-list-generator');

		function submitHandler(event) {
			// Prevent page refresh upon clicking the submit button.
			event.preventDefault();

			const list = document.querySelector('.full-costs-list');
			var startValue = document.getElementById('costlist-start').value,
				endValue = document.getElementById('costlist-end').value,
				totalItems = abs(startValue - endValue);

			// To prevent huge memory usage from the for loops, limit the amount
			// of list elements that can be generated to 2000. Trying to bypass
			// this limit by modifying the form's HTML will result in a warn
			// being logged in the console and the script not executing.
			if ((startValue || endValue) > 2000) {
				console.warn('[Colony cost list generator]: Aborted list generation due to attempting to show more than 2000 elements.');
				return;
			}

			// If the start value is higher than the end value, swap both values to
			// correctly generate the list anyways.
			if (startValue > endValue) [startValue, endValue] = [endValue, startValue];
			list.innerHTML = '';

			// Add a column parameter depending on the amount of list elements generated.
			// We use CSS to determine how the list will render depending on factors such
			// as the screen width and wether or not the right rail is collapsed/expanded.
			if (totalItems >= 500) {
				list.setAttribute('columns', '4');
			} else if (totalItems >= 200) {
				list.setAttribute('columns', '3');
			} else if (totalItems >= 50) {
				list.setAttribute('columns', '2');
			} else {
				list.removeAttribute('columns');
			}

			// Generate list elements and add them to the DOM.
			for (var i = startValue; i < endValue; i++) {
				const distance = i
					, cost = max(66000, ceil((2000 - 0.072 * distance) * distance * 31))
					, listElementHTML = '\
					<li>Distance: <b>' + formattedNumber(distance) +'</b>. Cost: <a href="/wiki/Resources" title="Resources"><img alt="Cost" src="https://galaxylife.fandom.com/Special:Filepath/Icon_costs.png" decoding="async" loading="lazy" width="18" height="14" data-image-name="Icon_costs.png" data-image-key="Icon_costs.png" data-src="https://galaxylife.fandom.com/Special:Filepath/Icon_costs.png?width=18" class="ls-is-cached lazyloaded"></a><b>' + formattedNumber(cost) + '</b>.</li>';

				list.insertAdjacentHTML('beforeend', listElementHTML);
			}
		}

		generator.addEventListener('submit', submitHandler);
	}

	// Cost generator - this script has only one input and returns it's in-game
	// cost. This is to quickly know the value of a single distance.
	function costGeneratorInit() {
		// Generate and add HTML to the DOM.
		const generatorHTML = '\
		<form class="cost-generator">\
			<label for="generator-distance">Distance: </label>\
			<input type="number" id="generator-distance" name="generator-distance" min="0" max="27777" required></input>\
			<input type="submit" class="wds-button cost-generator-submit" value="Get cost"></input>\
		</form>\
		<p class="cost-generator-output">Cost: </p>';

		costGeneratorContainer.insertAdjacentHTML('beforeend', generatorHTML);

		const generator = document.querySelector('.cost-generator'),
			input = document.getElementById('generator-distance'),
			output = document.querySelector('.cost-generator-output');

		function submitHandler(event) {
			// Prevent page refresh upon clicking the submit button.
			event.preventDefault();

			// Generate output.
			const distance = input.value
				, cost = max(66000, ceil((2000 - 0.072 * distance) * distance * 31));

			// Add output to the DOM.
			output.innerHTML = 'Cost: <a href="/wiki/Resources" title="Resources"><img alt="Cost" src="https://galaxylife.fandom.com/Special:Filepath/Icon_costs.png" decoding="async" loading="lazy" width="18" height="14" data-image-name="Icon_costs.png" data-image-key="Icon_costs.png" data-src="https://galaxylife.fandom.com/Special:Filepath/Icon_costs.png?width=18" class="ls-is-cached lazyloaded"></a><b>' + formattedNumber(cost) + '</b>.';
		}

		generator.addEventListener('submit', submitHandler);
	}

	// Colonization price calculator - this script takes multiple arguments which
	// are the player's X and Y coordinates of their planet along with the target
	// planet's X and Y coordinates. If the player has colonies, they can also
	// add them to take into account when determining the colonization cost, as it
	// will make a list of all player's coordinates and select the ones closest to
	// the target planet and return the colonization cost.
	function costCalculatorInit() {
		// Generate HTML and add it to the DOM. We also use a number as a variable
		// as to not read the form's HTML every time we have to change it.
		const calculatorHTML = '\
			<fieldset class="colony-cost-calculator-container">\
				<legend class="colony-cost-calculator-title">Colony cost calculator</legend>\
				<form class="colony-cost-calculator">\
					<div class="colony-cost-calculator-actions">\
						<input type="button" class="wds-button wds-is-secondary colony-add" value="Add colony"></input>\
						<input type="button" class="wds-button wds-is-secondary wds-is-disabled colony-remove" value="Remove colony"></input>\
						<input type="submit" class="wds-button colony-coords-submit" value="Calculate"></input>\
					</div>\
					<label class="planet-label">Your main planet\'s coordinates<br>\
						<input type="number" id="planet-X" name="planet-X" min="0" max="2000" placeholder="X" required>\
						<input type="number" id="planet-Y" name="planet-Y" min="0" max="2000" placeholder="Y" required>\
					</label>\
					<label class="target-label">Target planet\'s coordinates<br>\
						<input type="number" id="target-X" name="target-X" min="0" max="2000" placeholder="X" required>\
						<input type="number" id="target-Y" name="target-Y" min="0" max="2000" placeholder="Y" required>\
					</label>\
				</form>\
				<p class="colony-cost-output">Cost: <a href="/wiki/Resources" title="Resources"><img alt="Cost" src="https://galaxylife.fandom.com/Special:Filepath/Icon_costs.png" decoding="async" loading="lazy" width="18" height="14" data-image-name="Icon_costs.png" data-image-key="Icon_costs.png" data-src="https://galaxylife.fandom.com/Special:Filepath/Icon_costs.png?width=18" class="ls-is-cached lazyloaded"></a></p>\
			</fieldset>';
		var colonyNumber = 0;

		costsCalculator.insertAdjacentHTML('afterend', calculatorHTML);

		const form = document.querySelector('.colony-cost-calculator')
			, colonyAddButton = form.querySelector('.colony-add')
			, colonyRemoveButton = form.querySelector('.colony-remove');

		// When clicking the 'add colony' button, add two extra input fields where the
		// user can type the colony's X and Y coordinates.
		function addColony() {
			if (colonyNumber < 12) colonyNumber++;

			// Disable 'add colony' button when there's 11 colonies in the calculator.
			if (colonyNumber === 12) {
				if (!form.querySelector('.colony-add').classList.contains('wds-is-disabled')) {
					form.querySelector('.colony-add').classList.add('wds-is-disabled');
				}

				return;
			}

			// Add colony form HTML.
			const targetLabel = form.querySelector('.target-label')
				, colonyFormHTML = '\
				<label class="colony-label" id="colony-' + colonyNumber + '-label">Colony ' + colonyNumber + '\'s coordinates<br>\
					<input type="number" class="colony-x" id="colony-' + colonyNumber + '-X" name="target-' + colonyNumber + '-X" min="0" max="2000" placeholder="X" required>\
					<input type="number" class="colony-y" id="colony-' + colonyNumber + '-Y" name="target-' + colonyNumber + '-Y" min="0" max="2000" placeholder="Y" required>\
				</label>';

			targetLabel.insertAdjacentHTML('beforebegin', colonyFormHTML);

			// Re-enable the 'remove colony' button when a new colony has been added.
			if (form.querySelector('.colony-remove').classList.contains('wds-is-disabled')) {
				form.querySelector('.colony-remove').classList.remove('wds-is-disabled');
			}
		}

		// Remove colonies when clicking the 'remove colony' button.
		function removeColony() {
			// The 'if' check makes sure that there are colonies form(s) in the fieldset.
			// Otherwise this function (and, therefore, the button), won't do anything.
			if (form.querySelector('.colony-label')) {
				const colonies = form.querySelectorAll('.colony-label')
					, lastColony = colonies[colonies.length - 1];

				lastColony.remove();
				if (colonyNumber > 0) colonyNumber--;

				// Disable 'remove colony' button when no colony forms are present.
				if (colonyNumber === 0) {
					form.querySelector('.colony-remove').classList.add('wds-is-disabled');
				}

				// Re-enable 'add colony' button upon removing a colony form.
				if (colonyNumber < 11) {
					form.querySelector('.colony-add').classList.remove('wds-is-disabled');
				}
			}
		}

		function submitHandler(event) {
			// Prevent page refresh upon clicking the submit button.
			event.preventDefault();

			// Get values from input fields and return the coloniztion cost.
			const playerX = document.getElementById('planet-X').value
				, playerY = document.getElementById('planet-Y').value
				, targetX = document.getElementById('target-X').value
				, targetY = document.getElementById('target-Y').value
				, output = document.querySelector('.colony-cost-output')
				, colonies = document.querySelector('.colony-label');
			var distance = floor(sqrt(pow(playerX - targetX, 2) + pow(playerY - targetY, 2)));

			// If there are colonies in the fieldset, add some extra steps to take into
			// account these too. Otherwise proceed normally returning the cost of only
			// the first and it's target planet.
			if (colonies) calculateCostWithColonies();

			const cost = max(66000, ceil((2000 - 0.072 * distance) * distance * 31));

			// Update target planet's distance and cost.
			if (output.querySelector('.output-resources') || output.querySelector('.output-distance')) {
				output.querySelector('.output-resources').innerText = formattedNumber(cost);
				output.querySelector('.output-distance').innerText = formattedNumber(distance);
			} else {
				output.insertAdjacentHTML('beforeend', '<b class="output-resources">' + formattedNumber(cost) + '</b>. Distance: <b class="output-distance">' + formattedNumber(distance) + '</b>.');
			}

			// Function executed only when extra colonies are present in the fieldset.
			// This function makes a list of the player's coordinates (planets and colonies
			// alike) and uses the distance of the closest colony to the target planet to
			// get the final distance and cost.
			function calculateCostWithColonies() {
				const colonies = document.querySelectorAll('.colony-label') 
					, playerCoords = [[playerX, playerY]]
					, distances = [];

				colonies.forEach(function(colony) {
					const x = colony.querySelector('.colony-x').value
						, y = colony.querySelector('.colony-y').value;

					playerCoords.push([x, y]);
				});

				playerCoords.forEach(function(coord) {
					const distance = floor(sqrt(pow(coord[0] - targetX, 2) + pow(coord[1] - targetY, 2)));

					distances.push(distance);
				});

				distance = min.apply(Math, distances);
			}
		}

		colonyAddButton.addEventListener('click', addColony);
		colonyRemoveButton.addEventListener('click', removeColony);
		form.addEventListener('submit', submitHandler);
	}

	// Add commas to numbers with more than 4 digits where it corresponds.
	// Code from Stack Overflow: https://stackoverflow.com/a/2901298/20503138
	function formattedNumber(x) {
		return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
	}
}

function createModal(id, title, content) {
	const modalId = 'modal-' + id;

	if (!modals[modalId]) {
		modals[modalId] = new window.dev.modal.Modal({
			content: content,
			id: modalId,
			size: 'large',
			title: title
		});
	}

	modals[modalId].create();
}

function dragToScroll(target) {
	const container = target;
	var startY, startX, scrollLeft, scrollTop, isDown;

	container.addEventListener('mousedown', function(e) { mouseIsDown(e) });
	container.addEventListener('mouseup', function(e) { mouseUp(e) });
	container.addEventListener('mouseleave', function(e) { mouseLeave(e) });
	container.addEventListener('mousemove', function(e) { mouseMove(e) });

	function mouseIsDown(e) {
		isDown = true;
		startY = e.pageY - container.offsetTop;
		startX = e.pageX - container.offsetLeft;
		scrollLeft = container.scrollLeft;
		scrollTop = container.scrollTop; 
	}

	function mouseUp(e) {
		isDown = false;
	}

	function mouseLeave(e) {
		isDown = false;
	}

	function mouseMove(e) {
		if (isDown) {
			e.preventDefault();
			// Move vertcally.
			const y = e.pageY - container.offsetTop;
			const walkY = y - startY;
			container.scrollTop = scrollTop - walkY;

			// Move Horizontally.
			const x = e.pageX - container.offsetLeft;
			const walkX = x - startX;
			container.scrollLeft = scrollLeft - walkX;
		}
	}
}

function missionsModalsInit() {
	const missionChart = document.querySelector('#missions-chart > .overflow-wrapper');
	const missionIcons = document.querySelectorAll('.mission-icon');

	if (!missionIcons) return;

	missionIcons.forEach(function(missionIcon) {
		const id = missionIcon.getAttribute('id'),
			link = missionIcon.querySelector('a'),
			content = missionIcon.parentElement.querySelector('.modal-contents'),
			title = content.querySelector('.pi-title').innerText;

		if (!id || !link || !content || !title) return;

		link.removeAttribute('href');

		createModal(id, title, content);

		missionIcon.addEventListener('click', function(event) {
			modals['modal-' + id].show();
		});
	});

	dragToScroll(missionChart);
}

mw.hook('wikipage.content').add(function() {
	const pageName = mw.config.get('wgPageName');

	moveHistoryToDropdown();
	tableOverflowInit();
    animateOnView();
	newsInit();
	if (pageName === 'Colonies') colonyCostsInit();

	mw.loader.load( 'https://galaxylife.wiki.gg/index.php?title=MediaWiki:Tooltips.js&action=raw&ctype=text/javascript' );
	mw.loader.load( 'https://galaxylife.wiki.gg/index.php?title=MediaWiki:Modal.js&action=raw&ctype=text/javascript' );

	mw.hook('dev.modal').add(function(modal) {
		missionsModalsInit();
	});
});