Roll20 Fixes

Some silly fixes and 'improvements' to Roll20 because I'm impatient and a psycho

Pada tanggal 12 September 2018. Lihat %(latest_version_link).

// ==UserScript==
// @name			Roll20 Fixes
// @namespace		http://statonions.com/
// @version			0.2.1
// @description		Some silly fixes and 'improvements' to Roll20 because I'm impatient and a psycho
// @author			Justice Noon
// @match			https://app.roll20.net/editor/
// @grant			GM_setValue
// @grant			GM_getValue
// ==/UserScript==

(function() {
    'use strict';

//Features
var TMp = {
	clickShow: true,
	macroHide: true,
	fullButt: true,
	fixPing: true,
	css: {
		player:	false,
		zoom: false,
		sidebar: false,
		quick: false,
		turns: false,
		macroTok: true
	},
	pfCustomAttr: true,
	splitTokName: true
};
try {
	GM_info;
	var stored = GM_getValue('TMp', 'nah');
	if (stored == 'nah')
		GM_setValue('TMp', JSON.stringify(TMp));
	else
		TMp = JSON.parse(stored);
	checkLoaded();
}
catch(err) {
	checkLoaded();
}

//For *my* players specifically. Activates all css tweaks. I'm too lazy to maintain separate code (in-person touchpad view)
////_.each(TMp.css, (c,i,l) => l[i] = true);
function checkLoaded() {
	if (Campaign.gameFullyLoaded)
		showtime();
	else
		setTimeout(checkLoaded, 1000);
};

function showtime() {
	//Create greasemonkey preferences menu
	try {
		GM_info;
		var prefLi = document.createElement('li');
		prefLi.id = 'showfixes';
		var prefSpan = document.createElement('span');
		prefSpan.setAttribute('class', 'pictos');
		prefSpan.appendChild(document.createTextNode('x'));
		prefLi.appendChild(prefSpan);
		prefLi.appendChild(document.createTextNode("\r\n        'Fixes' Preferences\r\n"));
		document.getElementById('helpsite').childNodes[3].childNodes[1].appendChild(prefLi);
		prefLi.addEventListener('click', promptPrefs);

		function promptPrefs(e) {
			e.stopPropagation();
			var finished = false, response, ref = TMp, counter, changeKey;
			while (!finished) {
				counter = 1;
				response = parseInt(prompt(_.reduce(ref, (memo, v, k) => memo + '\n' + counter++ + ':' + k + '  ::  ' + (_.isObject(v) ? '[' + _.keys(v).length + ' properties]' : v), 'Enter a number:\n0:Back'), 'Enter a number to swap value.'));
				if (_.isNaN(response) || response == 0 || response > counter) {
					if (ref === TMp) {
						GM_setValue('TMp', JSON.stringify(TMp));
						finished = true;
						alert('Reload to see changes');
					}
					else
						ref = TMp;
				}
				else {
					changeKey = _.keys(ref)[response-1];
					if (_.isObject(ref[changeKey]))
						ref = ref[changeKey];
					else if (_.isBoolean(ref[changeKey]))
						ref[changeKey] = !ref[changeKey];
					else
						ref[changeKey] = prompt('Enter a new value for ' + changeKey);
				}
			}
		}
	}
	catch (err) {
		console.log('No GM. No need for preferences.');
	}

	//Display tokenname on select
	if (TMp.clickShow || TMp.css.macroTok) {
		var toks = [];
		var callback = function(mutationsList) {
			for(var mutation of mutationsList) {
				if (!_.isEmpty(mutation.addedNodes)) {
					let cct = currentcontexttarget.canvas;
					let cctIt = cct.lastRenderedObjectWithControlsAboveOverlay.model.attributes;
					if (!cctIt.showname && TMp.clickShow) {
						cctIt.showname = true;
						toks.push([cctIt.page_id, cctIt.id]);
					}
					cct._objects.push(cct._objects.splice(cct._objects.indexOf(cct.lastRenderedObjectWithControlsAboveOverlay), 1)[0]);
					currentcontexttarget.canvas.renderAll && currentcontexttarget.canvas.renderAll();
					//Second blank can be removed when notes aren't wrapped in <p>
					if (cctIt.gmnotes != 'blank' && cctIt.gmnotes != '%3Cp%3Eblank%3C/p%3E' && TMp.css.macroTok)
						_.each(document.getElementsByClassName('tokenactions')[0].firstElementChild.children, ob => ob.setAttribute('data-blank', 'false'));
				}
				else if (!_.isEmpty(mutation.removedNodes)) {
					_.each(toks, tok => {if (tok[0] == Campaign.activePage().id) _.find(Campaign.activePage().thegraphics.models, tokk => tokk.attributes.id == tok[1]).attributes.showname = false});
					toks = [];
					Campaign.activePage().loadPageIntoDOM();
				}
			}
		};
		var observer = new MutationObserver(callback);
		observer.observe(document.getElementById('editor-wrapper'), {childList: true});
	}

	//Allow Macros to be hidden
	if (TMp.macroHide) {
		var toggleMacro = document.createElement('div');
		toggleMacro.setAttribute('class', 'macrobox');
		var toggleButton = document.createElement('button');
		toggleButton.setAttribute('class', 'btn');
		toggleButton.appendChild(document.createTextNode('Show/Hide'));
		toggleButton.id = 'toggleMacros';
		toggleMacro.appendChild(toggleButton)
		//macrobar is defined by default
		macrobar.appendChild(toggleMacro);
		document.getElementById('toggleMacros').addEventListener('click', () => macrobar.style.left = (macrobar.style.left == '' ? (macrobar.offsetWidth * -1 + 100) + 'px' : ''));
	}

	//Create fullscreen button
	if (TMp.fullButt) {
		var fullScreenLi = document.createElement('li');
		fullScreenLi.setAttribute('tip', 'Toggle Fullscreen');
		fullScreenLi.id = 'fullscreener';
		var fullScreenSpan = document.createElement('span');
		fullScreenSpan.setAttribute('class', 'pictos');
		fullScreenSpan.appendChild(document.createTextNode('`'));
		fullScreenLi.appendChild(fullScreenSpan);
		floatingtoolbar.childNodes[1].appendChild(fullScreenLi);
		fullScreenLi.addEventListener('click', toggleFullScreen);

		function toggleFullScreen() {
			var doc = window.document;
			var docEl = doc.documentElement;

			var requestFullScreen = docEl.requestFullscreen || docEl.mozRequestFullScreen || docEl.webkitRequestFullScreen || docEl.msRequestFullscreen;
			var cancelFullScreen = doc.exitFullscreen || doc.mozCancelFullScreen || doc.webkitExitFullscreen || doc.msExitFullscreen;

			if(!doc.fullscreenElement && !doc.mozFullScreenElement && !doc.webkitFullscreenElement && !doc.msFullscreenElement) {
			requestFullScreen.call(docEl);
			}
			else {
			cancelFullScreen.call(doc);
			}
		}
	}

	//Fix Pings
	if (TMp.fixPing) {
		JSON.parse2 = JSON.parse;
		JSON.parse = function(e) {
			var intercept = JSON.parse2(e);
			if (intercept.type == 'mapping') {
				intercept.currentLayer = 'objects';
				if (intercept.pageid != Campaign.activePage().id)
					intercept.scrollto = false;
			}
			return intercept;
		};
	}

	//CSS override block
	if (_.contains(TMp.css, true)) {
		var style = document.createElement('style');
		style.type = 'text/css';
		style.innerHTML = '';
		//Remove player names/ icons
		if (TMp.css.player)
			style.innerHTML += '.player.ui-droppable {display: none;} ';
		//Remove Zoom Slider
		if (TMp.css.zoom)
			style.innerHTML += '#zoomslider {display:none;} ';
		//Remove sidebar show/ hide button
		if (TMp.css.sidebar)
			style.innerHTML += '#sidebarcontrol {display: none;} ';
		//Increase quickmenu sizes
		if (TMp.css.quick)
			style.innerHTML += '.sheet-roll-cell>a{padding:.5em !important;} ';
		//Permanently hide turn order
		if (TMp.css.turns)
			style.innerHTML += 'div.ui-dialog-buttons[style*="width: 160px;"] {display: none !important;} ';
		//Hide macroTokens by default
		if (TMp.css.macroTok)
			style.innerHTML += 'button[data-type="macro"]:not([data-blank="false"]) {display: none !important;} ';
		document.getElementsByTagName('head')[0].appendChild(style);
	}

	//Character sheets display custom attributes as name
	if (TMp.pfCustomAttr) {
		function cssInjection(charId) {
			var list = [1,2,3,10,11,12];
			var head = 'div.dialog.characterdialog[data-characterid=' + charId + '] {', foot = '';
			_.each(list, function(cK) {
				cK = 'customa' + cK;
				let val = {};
				if (!_.isUndefined(val = _.find(Campaign.characters._byId[charId].attribs.models, at => at.attributes.name == cK + '-name')))
					head += `--${cK}: "${val.attributes.current}"; `
				foot += `input[title="@{buff_${cK}-total}"] + span {visibility: hidden; top: -1.2rem;} input[title="@{buff_${cK}-total}"] + span::after {content: var(--${cK}, "${cK}"); visibility: visible; display: block;} `
			});
			return [head + '} ', foot];
		}
		function newShow(that) {
			var customOver;
			var modCss = cssInjection(that.model.attributes.id);
			if (_.isNull(document.getElementById('customOver'))) {
				customOver = document.createElement('style');
				customOver.type = 'text/css';
				customOver.id = 'customOver';
				customOver.innerHTML = modCss[1];
			}
			else {
				customOver = document.getElementById('customOver');
			}
			customOver.innerHTML += (modCss[0].indexOf('{}') > -1 ? '' : modCss[0]);
			if (that.childWindow)
				_.delay(function() {customOver.innerHTML = modCss.join(' '); window.allChildWindows[0].document.getElementsByTagName('head')[0].appendChild(customOver)}, 2000);
			else
				document.getElementsByTagName('head')[0].appendChild(customOver);
		};
		_.each(Campaign.characters.models, ch => {ch.view.showDialog2 = ch.view.showDialog; ch.view.showDialog = function(e, t) {this.showDialog2(e, t); newShow(this)}});
		_.each(Campaign.characters.models, ch => {ch.view.showPopout2 = ch.view.showPopout; ch.view.showPopout = function(e, t) {this.showPopout2(e, t); newShow(this)}});
		_.each(window.allChildWindows, win => {newShow(Campaign.characters._byId[win.document.getElementsByClassName('dialog characterdialog')[0].getAttribute('data-characterid')].view)});
	}

	//Tokens auto split their name to multiple lines
	if (TMp.splitTokName) {
		var can = document.getElementById('maincanvas').getContext('2d');
		can.fillRect2 = can.fillRect;
		can.fillRect = function(x, y, w, h) {if (this.font != 'bold 14px Arial' && this.fillStyle.indexOf('rgba(255,255,255,0.5') == -1) this.fillRect2(x, y, w, h);};
		can.fillText2 = can.fillText;
		can.fillText = function(t, x, y, m) {
			var g = 14, r = y - 22, n = m;
			var texes = t.split(' ');
			for (var k = 0; k < texes.length; k++) {
				n = this.measureText(texes[k]).width;
				while (this.measureText(texes[k] + ' ' + texes[k+1]).width < r*2 && k+1 < texes.length) {
					texes[k] += ' ' + texes[k+1];
					n = this.measureText(texes[k]).width;
					texes.splice(k+1, 1);
				}
				this.fillStyle = 'rgba(255, 255, 255, 0.50)';
				this.fillRect2(-1 * Math.floor((n + 6) / 2), r + 8 + (g+6)*k, n + 6, g + 6);
				this.fillStyle = 'rgb(0,0,0)';
				this.fillText2(texes[k], x, y + (g+6)*k, n);
			}
		};
	}
}

})();