HoloTools Comment Widener

Puts comments next to videos instead of inside of their boxes

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

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

Tendrás que instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Tendrás que instalar una extensión como Tampermonkey antes de poder instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Advertisement:

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

Advertisement:

// ==UserScript==
// @name         HoloTools Comment Widener
// @namespace    http://tampermonkey.net/
// @version      0.3
// @description  Puts comments next to videos instead of inside of their boxes
// @author       TellowKrinkle
// @match        https://hololive.jetri.co/
// @grant        none
// ==/UserScript==

(function() {
	'use strict';

	const videoPadding = 3; // px
	const containerPadding = 5; // px
	const commentWidth = 300; // px

	/** Check if HoloTools is in auto layout mode */
	function isAutoLayout() {
		let layoutMode = document.getElementsByClassName("layout-mode")[0];
		if (!layoutMode) { return true; } // If holotools updates and this breaks, say yes
		return layoutMode.innerHTML !== "unfold_more";
	}

	/**
	 * Helper for mass update of HTML nodes
	 * For each (string, function) pair, calls function on all elements with the class string
	 * @param {Object.<string, function(HTMLElement):void>} updates
	 */
	function updateByClass(updates) {
		for (const className of Object.keys(updates)) {
			for (const element of document.getElementsByClassName(className)) {
				updates[className](element);
			}
		}
	}

	/** Resize all videos to the given width */
	function resizeVideos(videoWidth, containerHeight, hasComments) {
		const height = videoWidth * 9 / 16;
		const containerWidth = hasComments ? videoWidth + 300 : videoWidth;
		updateByClass({
			"player-yt-box": (container) => {
				container.style.width = `${containerWidth}px`;
				container.style.height = `${containerHeight}px`;
			},
			"player-yt-frame": (video) => {
				video.style.top = "0px";
				video.children[0].style.top = "0px";
				video.children[0].style.width = `${videoWidth}px`;
				video.children[0].style.height = `${height}px`;
			},
			"player-yt-info": (info) => { info.style.display = "none"; }
		});
	}

	/** Calculates the size of the video and its player */
	function calculateVideoSize(videoCount, videosWide, containerWidth, containerHeight, hasComments) {
		const videosHigh = Math.ceil(videoCount / videosWide);
		const widthPadding = videosWide * videoPadding;
		const heightPadding = videosHigh * videoPadding;
		const videoWidth = Math.floor((containerWidth - widthPadding) / videosWide) - (hasComments ? commentWidth : 0);
		const videoHeight = Math.floor((containerHeight - heightPadding) / videosHigh);
		return {width: Math.min(videoWidth, Math.floor(videoHeight * 16 / 9)), commentHeight: videoHeight};
	}

	/** Calculates the optimal number of videos wide to get the best usage of space */
	function calculateOptimalVideosWide(videoCount, containerWidth, containerHeight, hasComments) {
		let best = calculateVideoSize(videoCount, 1, containerWidth, containerHeight, hasComments).width;
		for (let videosWide = 2; videosWide < 10; videosWide++) {
			let newHeight = calculateVideoSize(videoCount, videosWide, containerWidth, containerHeight, hasComments).width;
			if (newHeight > best) {
				best = newHeight;
			} else {
				return videosWide - 1;
			}
		}
		throw `calculateOptimalVideosWide broke or there were so many videos that the optimal layout was 10 or more wide`;
	}

	let lastVideoWidth = 0; // To track whether we need to rerun relayoutVideos

	/** Recalculates the proper video size and applies it */
	function relayoutVideos() {
		const container = document.getElementsByClassName("player-container")[0];
		const videoCount = container.children.length;
		const containerWidth = container.clientWidth - containerPadding * 2;
		const containerHeight = container.clientHeight - containerPadding * 2;
		const hasComments = document.getElementsByClassName("withChat").length !== 0;
		const currentVideosWide = /repeat\((\d+)/.exec(container.style["grid-template-columns"])[1] | 0;
		let videosWide = currentVideosWide;
		if (isAutoLayout()) {
			videosWide = calculateOptimalVideosWide(videoCount, containerWidth, containerHeight, hasComments);
		}
		if (videosWide !== currentVideosWide) {
			container.style["grid-template-columns"] = `repeat(${videosWide}, 1fr)`;
		}
		const videoSize = calculateVideoSize(videoCount, videosWide, containerWidth, containerHeight, hasComments);
		console.log(`Will resize videos to ${videoSize.width}px wide`);
		lastVideoWidth = videoSize.width;
		resizeVideos(videoSize.width, videoSize.commentHeight, hasComments);
	}

	// In case someone wants to manually call it
	window.relayoutVideos = relayoutVideos;

	const videoResizeObserver = new MutationObserver(function(records) {
		for (const record of records) {
			if (record.attributeName !== "style" && record.attributeName !== "class") { continue; }
			if (record.target.style.width !== lastVideoWidth + "px") {
				relayoutVideos();
				return;
			}
		}
	});

	/** @param {HTMLElement} element */
	function watchPlayerResize(element) {
		videoResizeObserver.observe(element.children[0], {attributes: true});
		for (const child of element.children[0].children) {
			for (const grandchild of child.children) {
				if (grandchild.nodeName === "IFRAME") {
					videoResizeObserver.observe(grandchild, {attributes: true});
				}
			}
		}
	}

	/** @param {HTMLElement} element */
	function watchVideoAddRemove(element) {
		const videoAddRemoveObserver = new MutationObserver(function(records) {
			for (const record of records) {
				for (const node of record.addedNodes) {
					watchPlayerResize(node);
				}
			}
		});

		for (const child of element.children) { watchPlayerResize(child); }
		videoAddRemoveObserver.observe(element, {childList: true});
	}

	/** Initializes the userscript */
	function setup() {
		// Stores the currently-being-watched object so we don't double-watch it
		let currentWatch = document.getElementsByClassName("player-container")[0];
		if (currentWatch) { watchVideoAddRemove(currentWatch); }
		const observer = new MutationObserver(function() {
			let playerContainer = document.getElementsByClassName("player-container");
			if (currentWatch !== playerContainer[0]) {
				currentWatch = playerContainer[0];
				if (playerContainer[0]) {
					watchVideoAddRemove(playerContainer[0]);
				}
			}
		});

		const mainApp = document.getElementsByClassName("md-content")[0];
		observer.observe(mainApp, {childList: true});
	}

	// Run on page load
	if (document.readyState !== "loading") {
		setup();
	} else {
		document.addEventListener("DOMContentLoaded", setup);
	}
})();