Hordes UI Mod

Various UI mods for Hordes.io.

当前为 2019-12-21 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Hordes UI Mod
// @version      0.1
// @description  Various UI mods for Hordes.io.
// @author       Sakaiyo
// @match        https://hordes.io/play
// @grant        GM_addStyle
// @namespace https://greasyfork.org/users/160017
// ==/UserScript==
/**
  * TODO: Implement GM and lvlup chat tabs
  * TODO: Implement saving of dragged UI location
  * TODO: (Maybe) Implement saving of chat filters
  * TODO: Implement saving of map and chat size
  * TODO: Implement chat tabs
  * TODO: Implement inventory sorting
  * TODO: (Maybe) Improved healer party frames
  * TODO: Can we speed up windows becoming draggable? Slight delay
  */
(function() {
    'use strict';

    const state = {};
    const CHAT_LVLUP_CLASS = 'js-chat-lvlup';
    const CHAT_GM_CLASS = 'js-chat-gm';

    // UPDATING STYLES BELOW - Must be invoked in main function
    GM_addStyle(`
    	/* Transparent chat bg color */
		.frame.svelte-1vrlsr3 {
			background: rgba(0,0,0,0.4);
		}

		/* Allows windows to be moved */
		.window {
			position: relative;
		}

		/* Enable chat & map resize */
		.js-chat-resize {
			resize: both;
			overflow: auto;
		}
		.js-map-resize:hover {
			resize: both;
			overflow: auto;
			direction: rtl;
		}

		/* The browser resize icon */
		*::-webkit-resizer {
	        background: linear-gradient(to right, rgba(51, 77, 80, 0), rgba(203, 202, 165, 0.5));
		    border-radius: 8px;
		    box-shadow: 0 1px 1px rgba(0,0,0,1);
		}
		*::-moz-resizer {
	        background: linear-gradient(to right, rgba(51, 77, 80, 0), rgba(203, 202, 165, 0.5));
		    border-radius: 8px;
		    box-shadow: 0 1px 1px rgba(0,0,0,1);
		}
	`);


    // MAIN MODS BELOW
    const setupDom = {
    	// Commenting this out for now, the added UI doesn't do anything yet
    // 	newChatFilters: () => {
	   //  	const $channelselect = document.querySelector('.channelselect');
	   //  	if (!document.querySelector(`.${CHAT_LVLUP_CLASS}`)) {
		  //       const $lvlup = createElement({
		  //       	element: 'small',
		  //       	class: `btn border black textgrey ${CHAT_LVLUP_CLASS}`,
		  //       	content: 'lvlup'
		  //       });
		  //       $channelselect.appendChild($lvlup);
	   //  	}
	   //  	if (!document.querySelector(`.${CHAT_GM_CLASS}`)) {
				// const $gm = createElement({
		  //       	element: 'small',
		  //       	class: `btn border black textgrey ${CHAT_GM_CLASS}`,
		  //       	content: 'GM'
		  //       });
		  //       $channelselect.appendChild($gm);
		  //   }
	   //  },
    };

    const wireDom = {
    	newChatFilters: () => {

    	},

    	// Drag all windows by their header
    	draggableUIWindows: () => {
    		Array.from(document.querySelectorAll('.window:not(.js-can-move)')).forEach($window => {
				dragElement($window, $window.querySelector('.titleframe'));
				$window.classList.add('js-can-move');
    		});	
    	},

    	// Resize chat and map
    	resizableUi: () => {
    		document.querySelector('#chat').parentNode.classList.add('js-chat-resize');

    		const $map = document.querySelector('.svelte-hiyby7');
    		$map.classList.add('js-map-resize');

    		// On resize of map, resize canvas to match
    		const resizeObserver = new ResizeObserver(() => {
    			// Get real values of map height/width, excluding padding/margin/etc
    			let mapWidth = window.getComputedStyle($map, null).getPropertyValue('width');
    			let mapHeight = window.getComputedStyle($map, null).getPropertyValue('height');
    			mapWidth = Number(mapWidth.slice(0, -2));
    			mapHeight = Number(mapHeight.slice(0, -2));

    			// If height/width are 0 or unset, don't resize canvas
    			if (!mapWidth || !mapHeight) {
    				return;
    			}

    			const $canvas = $map.querySelector('canvas');
    			if ($canvas.width !== mapWidth) {
    				$canvas.width = mapWidth;
    			}

    			if ($canvas.height !== mapHeight) {
    				$canvas.height = mapHeight;
    			}
    		})
    		resizeObserver.observe($map);
    	},
    };

    // Add new DOM, wire it up, then continuously rerun specific methods whenever UI changes
    function initialize() {
        Object.keys(setupDom).forEach((domMethod) => setupDom[domMethod]());
        Object.keys(wireDom).forEach((domMethod) => wireDom[domMethod]());

        // Continuously re-run specific wireDom methods that need to be executed on UI change
        const rerunOnChange = () => {
        	// If new window appears, e.g. even if window is closed and reopened, we need to rewire it
        	wireDom.draggableUIWindows();
        };
		document.querySelector('.layout').addEventListener('click', rerunOnChange);
		document.querySelector('.layout').addEventListener('keypress', rerunOnChange);
    }

    // Initialize mods once UI DOM has loaded
    const pageObserver = new MutationObserver((_, observer) => {
	    const isUiLoaded = !!document.querySelector('.layout');
	    if (isUiLoaded) {
	    	initialize();
	    }
	});
	pageObserver.observe(document.body, { attributes: true, childList: true })

	// UTIL METHODS
	// Nicer impl to create elements in one method call
	function createElement(args) {
		const $node = document.createElement(args.element);
		if (args.class) { $node.className = args.class; }
		if (args.content) { $node.innerHTML = args.content; }
		if (args.src) { $node.src = args.src; }
		return $node;
	}

	// ...Can't remember why I added this.
	// TODO: Remove this if not using. Can access chat input with it
	function simulateEnterPress() {
		const kbEvent = new KeyboardEvent("keydown", {
		    bubbles: true, cancelable: true, keyCode: 13
		});
		document.body.dispatchEvent(kbEvent);
	}

	// Credit: https://stackoverflow.com/a/14234618 (Has been slightly modified)
	// $draggedElement is the item that will be dragged.
	// $dragTrigger is the element that must be held down to drag $draggedElement
	function dragElement($draggedElement, $dragTrigger) {
		let offset = [0,0];
		let isDown = false;
		$dragTrigger.addEventListener('mousedown', function(e) {
		    isDown = true;
		    offset = [
		        $draggedElement.offsetLeft - e.clientX,
		        $draggedElement.offsetTop - e.clientY
		    ];
		}, true);
		document.addEventListener('mouseup', function() {
		    isDown = false;
		}, true);

		document.addEventListener('mousemove', function(e) {
		    event.preventDefault();
		    if (isDown) {
		        $draggedElement.style.left = (e.clientX + offset[0]) + 'px';
		        $draggedElement.style.top  = (e.clientY + offset[1]) + 'px';
		    }
		}, true);
	}
})();