您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Internet Roadtrip Speedometer.
// ==UserScript== // @name Internet Roadtrip Speedometer // @namespace spideramn.github.io // @match https://neal.fun/internet-roadtrip/* // @version 0.0.3 // @author Spideramn // @description Internet Roadtrip Speedometer. // @license MIT // @grant GM.addStyle // @grant GM.setValues // @grant GM.getValues // @run-at document-start // @icon https://neal.fun/favicons/internet-roadtrip.png // @require https://cdn.jsdelivr.net/npm/[email protected] // @require https://cdnjs.cloudflare.com/ajax/libs/gauge.js/1.3.9/gauge.min.js // ==/UserScript== // This works together with irf.d.ts to give us type hints /* globals IRF Gauge */ /** * Internet Roadtrip Framework * @typedef {typeof import('internet-roadtrip-framework')} IRF */ (async function() { 'use strict'; if (!IRF?.isInternetRoadtrip) { return; } // Get map methods and various objects const odometer = await IRF.vdom.odometer; const wheelDom = await IRF.dom.wheel; // Speedometer class SpeedOmeterControl { _queue = []; _speedOmeterContainer = undefined; _speedOmeterElement = undefined; _gauge = undefined; addDistance(distanceInMiles) { // prevent duplicate distances if(this._queue.length > 0) { const lastDistance = this._queue[this._queue.length - 1].distance; if(distanceInMiles == lastDistance) { this.update(); return; } } this._queue.push({time: Date.now(), distance: distanceInMiles}); if (this._queue.length > 15) { this._queue.shift(); } this.update(); }; async setup() { GM.addStyle(` .speedOmeterContainer { width: 300px; // 70%; height: 100px; // 25%; position: absolute; top: 30px; // 10%; left: 35px; // 15%; visibility: hidden; } .speedOmeterContainer canvas{ position: absolute; width: 100%; height: 100%; } .speedOmeterContainer span { position: absolute; width: 100%; bottom: 0%; left: 0%; height: 50%; text-align: center; font-family: Roboto,sans-serif; color: #1F1F1F; font-size: 17px; } `); // add dom elements this._speedOmeterContainer = document.createElement('div'); this._speedOmeterContainer.className = "speedOmeterContainer"; const speedOmeterCanvas = document.createElement('canvas'); this._speedOmeterElement = document.createElement('span'); this._speedOmeterContainer.appendChild(speedOmeterCanvas); this._speedOmeterContainer.appendChild(this._speedOmeterElement); wheelDom.prepend(this._speedOmeterContainer); var opts = { angle: 0, lineWidth: 1, radiusScale: 1, pointer: { length: 1, strokeWidth: 0.035, color: '#AA000099' }, limitMax: true, limitMin: true, generateGradient: false, highDpiSupport: true, colorStart: '#FFFFFF99', colorStop: '#FFFFFF99', strokeColor: '#FFFFFF99', // renderTicks is Optional renderTicks: { divisions: 6, divWidth: 2, divLength: 0.1, divColor: '#333333', subDivisions: 5, subLength: 0.05, subWidth: 1, subColor: '#666666' }, staticLabels: { font: "10px Roboto,sans-serif", labels: [0, 5, 10, 15, 20, 25, 30], color: "#000000", fractionDigits: 0 } }; this._gauge = new Gauge(speedOmeterCanvas).setOptions(opts); this._gauge.maxValue = 30; this._gauge.set(0); // set actual value // set hook odometer.state.updateDisplay = new Proxy(odometer.methods.updateDisplay, { apply: (target, thisArg, args) => { this.addDistance(odometer.props.miles); return Reflect.apply(target, thisArg, args);; }, }); // update position of speedometer this.updatePosition(); } update() { if(settings.speedometer_enabled) { this._speedOmeterContainer.style.visibility = 'visible'; if(this._queue.length < 3) { this._gauge.set(0); this._speedOmeterElement.innerText = 'Calibrating...'; return; } const lastItem = this._queue[this._queue.length - 1]; const firstItem = this._queue[0]; const timeInHours = (lastItem.time - firstItem.time) / (1000 * 60 * 60); let speed = (lastItem.distance - firstItem.distance) / timeInHours; // in mph if(odometer.data.isKilometers) { speed *= odometer.data.conversionFactor; } this._gauge.set(speed); this._speedOmeterElement.innerText = (speed.toFixed(1) + (odometer.data.isKilometers? ' km/h' : ' mph')); } else { this._gauge.set(0); this._speedOmeterElement.innerText = ''; this._speedOmeterContainer.style.visibility = 'hidden'; } } updatePosition() { if(this._speedOmeterContainer) { this._speedOmeterContainer.style.left = settings.speedometer_left + 'px'; this._speedOmeterContainer.style.top = settings.speedometer_top + 'px'; this._speedOmeterContainer.style.transform = 'scale(' + settings.speedometer_scale + ')'; } } }; // // Settings const settings = { "speedometer_enabled": true, "speedometer_scale": 1.0, "speedometer_left": 35, "speedometer_top": 30, }; const storedSettings = await GM.getValues(Object.keys(settings)) Object.assign(settings, storedSettings); await GM.setValues(settings); // settings panel let gm_info = GM.info gm_info.script.name = "Speedometer" const irf_settings = IRF.ui.panel.createTabFor(gm_info, { tabName: "Speedometer" }); add_checkbox('Display speedometer', 'speedometer_enabled', () => speedOmeter.update()); const header = document.createElement('h3'); header.innerText = 'Position'; irf_settings.container.appendChild(header); let button = document.createElement('button'); button.innerText = 'Reset position'; button.addEventListener('click', () => { speedometer_left_element.value = settings.speedometer_left = 35; speedometer_top_element.value = settings.speedometer_top = 30; speedometer_scale_element.value = settings.speedometer_scale = 1.0; GM.setValues(settings); speedOmeter.updatePosition(); }); irf_settings.container.appendChild(button); irf_settings.container.appendChild(document.createElement('br')); const speedometer_left_element = add_numeric('Left', 'speedometer_left', -1000, 1000, 1, () => speedOmeter.updatePosition()); const speedometer_top_element = add_numeric('Top', 'speedometer_top', -1000, 1000, 1, () => speedOmeter.updatePosition()); const speedometer_scale_element = add_numeric('Scale', 'speedometer_scale', 0.5, 2, 0.1, () => speedOmeter.updatePosition()); function add_checkbox(name, identifier, callback=undefined, settings_container=irf_settings.container) { let label = document.createElement("label"); let checkbox = document.createElement("input"); checkbox.type = "checkbox"; checkbox.checked = settings[identifier]; checkbox.className = IRF.ui.panel.styles.toggle; label.appendChild(checkbox); let text = document.createElement("span"); text.innerText = " " + name; label.appendChild(text); checkbox.addEventListener("change", () => { settings[identifier] = checkbox.checked; GM.setValues(settings); if (callback) callback(checkbox.checked); }); settings_container.appendChild(label); settings_container.appendChild(document.createElement("br")); settings_container.appendChild(document.createElement("br")); return checkbox }; function add_numeric(name, identifier, min, max, step, callback=undefined, settings_container=irf_settings.container) { let label = document.createElement("label"); label.style.display = 'block'; let text = document.createElement("span"); text.innerText = " " + name; label.appendChild(text); label.appendChild(document.createElement("br")); let input = document.createElement("input"); input.id = "speedometer_" + identifier; input.type = "number"; input.value = settings[identifier]; input.min = min; input.max = max; input.step = step; input.className = IRF.ui.panel.styles.input; label.appendChild(input); input.addEventListener("change", () => { settings[identifier] = parseFloat(input.value); GM.setValues(settings); if (callback) callback(parseFloat(input.value)); }); settings_container.appendChild(label); return input; }; const speedOmeter = new SpeedOmeterControl(); await speedOmeter.setup(); })();