Moves the vote counts in neal.fun/internet-roadtrip from the top right panel to be alongside the arrows, on the wheel, and in the radio
当前为
// ==UserScript== // @name Internet Roadtrip - Combined Votes Counts UI // @description Moves the vote counts in neal.fun/internet-roadtrip from the top right panel to be alongside the arrows, on the wheel, and in the radio // @namespace me.netux.site/user-scripts/internet-roadtrip/combined-votes-counts-ui // @version 1.1 // @author Netux // @license MIT // @match https://neal.fun/internet-roadtrip/* // @icon https://neal.fun/favicons/internet-roadtrip.png // @run-at document-end // @grant none // @require https://cdn.jsdelivr.net/combine/npm/@violentmonkey/dom@2,npm/@violentmonkey/[email protected],npm/[email protected] // ==/UserScript== /* globals IRF */ (async () => { { const styleEl = document.createElement('style'); styleEl.innerText = ` .container { & .results { top: 50px; right: 10px; width: fit-content; padding: 7px 10px; } & .options { & .option-votes { position: absolute; font-family: "Roboto", sans-serif; font-size: 12px; color: white; text-shadow: 1px 1px 2px black; bottom: -0.4em; left: 50%; translate: -50% 0; pointer-events: none; white-space: nowrap; } } & .wheel-container { & .wheel-honk-votes { position: absolute; top: 22%; left: 50%; translate: -50%; font-family: "Roboto", sans-serif; font-size: 20px; color: white; text-shadow: 1px 1px 2px black; white-space: nowrap; pointer-events: none; } } } @media screen and (max-width: 900px) { .container { & .results { top: 41px; right: 5px; } } } `; document.head.appendChild(styleEl); } const containerVDOM = await IRF.vdom.container; const resultsEl = await IRF.dom.results; const resultsVDOM = await IRF.vdom.results; const optionsContainerEl = await IRF.dom.options; const wheelContainerEl = await IRF.dom.wheel; const radioEl = await IRF.dom.radio; const wheelHonkVotesEl = document.createElement('span'); const radioSeekVotesTextNode = document.createTextNode('0'); function ensureOptionVotesEl(optionEl) { let votesEl = optionEl._votesEl; if (!votesEl) { votesEl = document.createElement('span'); votesEl.className = 'option-votes'; votesEl.textContent = `0 (0%)`; optionEl.appendChild(votesEl); optionEl._votesEl = votesEl; } return votesEl; } function updateVotes(votes) { const totalVotes = Object.values(votes).reduce((total, count) => total + count, 0); const optionEls = optionsContainerEl.querySelectorAll('.option'); for (const [voteStr, votesCount] of Object.entries(votes)) { const percentageStr = `${totalVotes === 0 ? 0 : Math.floor(votesCount / totalVotes * 100)}%`; switch (voteStr) { case "-2": { wheelHonkVotesEl.textContent = `${votesCount} (${percentageStr})`; break; } case "-1": { radioSeekVotesTextNode.textContent = votesCount; break; } default: { const voteIndex = parseInt(voteStr, 10); const optionEl = optionEls[voteIndex]; if (!optionEl) { continue; } const votesEl = ensureOptionVotesEl(optionEl); votesEl.textContent = `${votesCount} (${percentageStr})`; } } } } { const { set: voteCountsSetter } = Object.getOwnPropertyDescriptor(resultsVDOM.state._props, 'voteCounts'); Object.defineProperty(resultsVDOM.state._props, 'voteCounts', { set(newVoteCounts) { updateVotes(newVoteCounts); return voteCountsSetter.call(this, newVoteCounts); }, configurable: true, enumerable: true, }); } { const optionsContainerMutationObserver = new MutationObserver((records) => { for (const record of records) { if (record.type !== "childList") { continue; } for (const addedOption of record.addedNodes) { if (addedOption.className !== 'option') { continue; } ensureOptionVotesEl(); } } }); optionsContainerMutationObserver.observe(optionsContainerEl, { childList: true }); const wheelClickArealEl = wheelContainerEl.querySelector('.wheel-click-area'); wheelHonkVotesEl.className = 'wheel-honk-votes'; wheelClickArealEl.appendChild(wheelHonkVotesEl); const radioSeekButtonLabelEl = radioEl.querySelector('.control-button .button-label'); radioSeekButtonLabelEl.append( document.createTextNode(' ('), radioSeekVotesTextNode, document.createTextNode(')'), ); const resultsContentEl = resultsEl.querySelector('.results-content'); resultsContentEl.remove(); } })();