KvByte / Tinkercad Mobile

// ==UserScript==
// @namespace     https://openuserjs.org/users/KvByte
// @name          Tinkercad Mobile
// @description   Converts touch to mouse events, and adjust the interface for mobile.
// @copyright     2021, KvByte (https://openuserjs.org/users/KvByte)
// @license       MIT
// @version       0.1.07
// @include       https://www.tinkercad.com/things/*
// @icon          https://www.google.com/s2/favicons?domain=tinkercad.com
// @grant         none
// @noframes
// ==/UserScript==

// ==OpenUserJS==
// @author KvByte
// ==/OpenUserJS==

(function () {
	'use strict';
	console.log("Start User Script");

	// Based on 'Mickey Shine' solution, created Tap to "click", and updated from initMouseEvent (deprecated) to MouseEvent (new)
	// https://stackoverflow.com/questions/1517924/javascript-mapping-touch-events-to-mouse-events
	let lastTouchStart = 0;
	function touchToMouseHandler(event) {
		// Get first touch input
		let touches = event.changedTouches,
			first = touches[0],
			type = "";

		// Get the correct type to simulate
		switch (event.type) {
			case "touchstart": type = "mousedown"; lastTouchStart = new Date().getTime(); break;
			case "touchmove": type = "mousemove"; break;
			case "touchend": type = "mouseup"; break;
			case "touchend": type = "mouseup"; break;
			default: return;
		}

		// dispatch the simulated type
		const mouseEventInitDictionary = {
			bubbles: true,
			cancelable: true,
			clientX: first.clientX,
			clientY: first.clientY,
			screenX: first.screenX,
			screenY: first.screenY
		}
		const mouseEvent = new MouseEvent(type, mouseEventInitDictionary);
		first.target.dispatchEvent(mouseEvent);

		// Convert Tap to "click" event
		if (type == "mouseup") {
			const currentTime = new Date().getTime();
			if (currentTime - lastTouchStart <= 200 /*ms*/) {
				// dispatch the click type
				const clickEvent = new MouseEvent("click", mouseEventInitDictionary);
				first.target.dispatchEvent(clickEvent);
			}
		}

		// cancels the bubbling of the touch event
		event.preventDefault();
	}

	function initTouchToMouseConverter(el) {
		el.addEventListener("touchstart", touchToMouseHandler, true);
		el.addEventListener("touchmove", touchToMouseHandler, true);
		el.addEventListener("touchend", touchToMouseHandler, true);
		el.addEventListener("touchcancel", touchToMouseHandler, true);
	}

	// Tinkercad
	let tinkercadVisualChanges = () => {
		console.log("Tinkercad Changes Start");

		// Start Mouse Simulation
		initTouchToMouseConverter(document.querySelector("#breadboardTab svg"));


		// CSS
		var style = document.createElement('style');
		style.innerHTML = `
			/* Dont scroll body */
			body {
				touchAction: none;
				overflow: hidden;
			}

			/* Remove time counting in simulation */
			.sitemenu__info{
				display: none;
			}

			/* Remove text button */
			#CODE_EDITOR_ID .circ_btn__txt{
				display: none;
			}

			/* Increase size of collapseButton */
			.editor__sidebar__collapseButton{
				width: 25px;
				left: -25px;
			}

			/* Hide Inspector under components */
			.inspector{
				right: 25px !important;
			}

			/* ZOOM buttons */
			#ZOOM_IN_, #ZOOM_OUT_{
				line-height: 32px;
				text-align: center;
				font-size: 25px;
				user-select: none;
				-moz-user-select: none;
				-webkit-user-select: none;
			}

			/* Full Screen */
			#FULLSCREEN_{

			}
			.button-fullscreen-svg- {
				background: url('data:image/svg+xml;utf8,<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="expand-arrows-alt" class="svg-inline--fa fa-expand-arrows-alt fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M448 344v112a23.94 23.94 0 0 1-24 24H312c-21.39 0-32.09-25.9-17-41l36.2-36.2L224 295.6 116.77 402.9 153 439c15.09 15.1 4.39 41-17 41H24a23.94 23.94 0 0 1-24-24V344c0-21.4 25.89-32.1 41-17l36.19 36.2L184.46 256 77.18 148.7 41 185c-15.1 15.1-41 4.4-41-17V56a23.94 23.94 0 0 1 24-24h112c21.39 0 32.09 25.9 17 41l-36.2 36.2L224 216.4l107.23-107.3L295 73c-15.09-15.1-4.39-41 17-41h112a23.94 23.94 0 0 1 24 24v112c0 21.4-25.89 32.1-41 17l-36.19-36.2L263.54 256l107.28 107.3L407 327.1c15.1-15.2 41-4.5 41 16.9z"></path></svg>') no-repeat;
				background-size: 20px 20px;
				background-position: 5px 5px;
			}
		`;
		document.head.appendChild(style);

		// Add ZOOM buttons in HTML
		let htmlZoomInOut = `
			<a id="ZOOM_IN_" class="zoomToFit js-zoomToFit viewcube__button viewcube__button__border js-analytics" style="top: 48px;left:8px;" title="Aumentar zoom">+</a>
			<a id="ZOOM_OUT_" class="zoomToFit js-zoomToFit viewcube__button viewcube__button__border js-analytics" style="top: 88px;left:8px;" title="Diminuir zoom">-</a></div>
		`;
		document.querySelector("#viewcube").insertAdjacentHTML('beforeend', htmlZoomInOut);
		// Set values
		const scrollEl = document.querySelector("#breadboardTab svg");
		const docCenterHeight = document.defaultView.innerHeight / 2;
		const docwidth = document.defaultView.innerWidth;
		const sidebarColapsed = () => document.querySelectorAll(".editor__sidebar__collapseButton_collapsed").length;
		const docCenterwidth = () => sidebarColapsed() ? docwidth / 2 : (docwidth - 276) / 2;
		const wheelEventInitDictionary = {
			bubbles: true,
			cancelable: true,
			clientY: docCenterHeight,
			screenY: docCenterHeight,
			pageY: docCenterHeight
		}
		// Zoom Functions
		document.querySelector("#ZOOM_IN_").onclick = () => scrollEl.dispatchEvent(new WheelEvent("wheel", {
			...wheelEventInitDictionary,
			clientX: docCenterwidth(),
			screenX: docCenterwidth(),
			pageX: docCenterwidth(),
			deltaY: -400
		}));
		document.querySelector("#ZOOM_OUT_").onclick = () => scrollEl.dispatchEvent(new WheelEvent("wheel", {
			...wheelEventInitDictionary,
			clientX: docCenterwidth(),
			screenX: docCenterwidth(),
			pageX: docCenterwidth(),
			deltaY: 400
		}));

		// Add Fullscreen button
		let htmlFullScreen = `
		<a id="FULLSCREEN_" class="zoomToFit js-zoomToFit viewcube__button viewcube__button__border js-analytics" style="top: 128px;left:8px;" title="Tela cheia">
			<div class="button-container-svg button-fullscreen-svg-"></div>
		</a>
		`;
		document.querySelector("#viewcube").insertAdjacentHTML('beforeend', htmlFullScreen);
		// Zoom Functions
		function toggleFullscreen(elem) {
			elem = elem || document.documentElement;
			if (!document.fullscreenElement){
				if (elem.requestFullscreen) {
					elem.requestFullscreen();
				}
			} else {
				if (document.exitFullscreen) {
					document.exitFullscreen();
				}
			}
		}
		document.querySelector("#FULLSCREEN_").onclick = () => toggleFullscreen();

	};

	const canStart = () => {
		if (document.querySelector(".loading") && document.querySelector(".loading").style.display === "none") {
			setTimeout(tinkercadVisualChanges, 1000);
		} else {
			setTimeout(canStart, 1000);
		}
	}

	setTimeout(canStart, 2000);

})();