|
// ==UserScript== |
|
// @name Internet Roadtrip - Vote History |
|
// @namespace me.netux.site/user-scripts/internet-roadtrip/vote-history |
|
// @match https://neal.fun/internet-roadtrip/* |
|
// @grant none |
|
// @version 1.5.1 |
|
// @author Netux |
|
// @icon https://www.google.com/s2/favicons?sz=64&domain=neal.fun |
|
// ==/UserScript== |
|
|
|
(() => { |
|
const MAX_ENTRIES = 8; |
|
|
|
const containerEl = document.querySelector('.container'); |
|
|
|
const state = { |
|
dom: {} |
|
} |
|
|
|
function start(vue) { |
|
state.vue = vue; |
|
|
|
setupDom(); |
|
patch(vue); |
|
|
|
/* |
|
for (let i = 0; i < 5; i++) { |
|
const vote = -2; |
|
addVote(vote, 0, { [vote]: { heading: 180 } }, { [vote]: 100, [9999]: 50 }); |
|
} |
|
*/ |
|
} |
|
|
|
function setupDom() { |
|
injectStylesheet(); |
|
|
|
state.dom.voteHistoryContainerEl = document.createElement('ul'); |
|
state.dom.voteHistoryContainerEl.className = 'vote-history'; |
|
containerEl.appendChild(state.dom.voteHistoryContainerEl); |
|
} |
|
|
|
function injectStylesheet() { |
|
const styleEl = document.createElement('style'); |
|
|
|
styleEl.textContent = ` |
|
.vote-history { |
|
position: fixed; |
|
right: 10px; |
|
top: 350px; |
|
margin: 0; |
|
padding: 0; |
|
list-style: none; |
|
color: white; |
|
font-family: "Roboto", sans-serif; |
|
font-size: 0.8rem; |
|
text-align: right; |
|
user-select: none; |
|
|
|
& .vote-history-entry { |
|
margin: 0.5rem 0; |
|
text-shadow: 1px 1px 2px black; |
|
|
|
& .vote-history-entry__icon-container { |
|
position: relative; |
|
height: 12px; |
|
aspect-ratio: 1; |
|
margin-right: 0.5rem; |
|
vertical-align: middle; |
|
display: inline-block; |
|
|
|
& > img { |
|
position: absolute; |
|
top: 0; |
|
left: 0; |
|
width: 100%; |
|
height: 100%; |
|
} |
|
|
|
& .vote-history-entry__icon-shadow { |
|
opacity: .5; |
|
scale: 1.5; |
|
filter: blur(5px); |
|
} |
|
|
|
& .vote-history-entry__icon { |
|
filter: invert(1); |
|
z-index: 1; |
|
} |
|
} |
|
|
|
& .vote-history-entry__time { |
|
margin-left: 1ch; |
|
font-size: 80%; |
|
color: lightgrey; |
|
} |
|
|
|
${new Array(MAX_ENTRIES).fill(null).map((_, i) => ` |
|
&:nth-child(${i + 1}) { |
|
opacity: ${1.2 - Math.pow(1 - ((MAX_ENTRIES - i) / MAX_ENTRIES), 2)}; |
|
} |
|
`).join('\n\n')} |
|
} |
|
} |
|
`; |
|
|
|
document.head.appendChild(styleEl); |
|
} |
|
|
|
function patch(vue) { |
|
const ogChangeStop = vue.changeStop; |
|
vue.changeStop = function (_, currentChosen) { |
|
if (!this.isChangingStop) { |
|
try { |
|
addVote(currentChosen, this.currentHeading, this.currentOptions, this.voteCounts); |
|
} catch (error) { |
|
console.error('Could not add vote:', { currentChosen, currentHeading: this.currentHeading, currentOptions: this.currentOptions, voteCounts: this.voteCounts }, error); |
|
} |
|
} |
|
|
|
return ogChangeStop.apply(this, arguments); |
|
} |
|
} |
|
|
|
function addVote(vote, heading, options, voteCounts) { |
|
function angleDifference(a, b) { |
|
var t = (b - a) % 360; |
|
if (t > 180) t -= 360; |
|
if (t < -180) t += 360; |
|
return t; |
|
} |
|
function getRotation(vote) { |
|
return 0.8 * angleDifference(heading, options[vote].heading); |
|
} |
|
|
|
const newEntryEl = document.createElement('li'); |
|
newEntryEl.className = 'vote-history-entry'; |
|
|
|
let entryActionText = '?'; |
|
let entryIconSrc = null; |
|
let entryIconRotation = 0; |
|
switch (vote) { |
|
case -2: { |
|
entryActionText = "HONK!"; |
|
entryIconSrc = '/internet-roadtrip/honk.svg' |
|
break; |
|
} |
|
case -1: { |
|
entryActionText = "Seek Radio"; |
|
entryIconSrc = '/internet-roadtrip/skip.svg' |
|
break; |
|
} |
|
default: { |
|
entryIconSrc = '/internet-roadtrip/chevron-black.svg'; |
|
if (options[vote]) { |
|
entryActionText = options[vote].description; |
|
entryIconRotation = getRotation(vote); |
|
} else { |
|
entryActionText = 'Turn Around'; |
|
entryIconRotation = (-heading) % 360; |
|
} |
|
break; |
|
} |
|
} |
|
|
|
const voteIconEl = document.createElement('div'); |
|
voteIconEl.className = 'vote-history-entry__icon-container'; |
|
if (entryIconRotation !== 0) { |
|
voteIconEl.style.rotate = `${entryIconRotation}deg`; |
|
} |
|
newEntryEl.appendChild(voteIconEl); |
|
|
|
const voteIconShadowImageEl = document.createElement('img'); |
|
voteIconShadowImageEl.className = 'vote-history-entry__icon-shadow'; |
|
voteIconShadowImageEl.src = entryIconSrc; |
|
voteIconEl.appendChild(voteIconShadowImageEl); |
|
|
|
const voteIconImageEl = document.createElement('img'); |
|
voteIconImageEl.className = 'vote-history-entry__icon'; |
|
voteIconImageEl.src = entryIconSrc; |
|
voteIconEl.appendChild(voteIconImageEl); |
|
|
|
const voteCount = voteCounts[vote]; |
|
const entryVotesText = voteCount != null |
|
? `${voteCount} vote${voteCount === 1 ? '' : 's'}, ${Math.round(voteCount / Object.values(voteCounts).reduce((acc, votes) => acc + votes, 0) * 100)}%` |
|
: 'no votes'; |
|
const entryTextNode = document.createTextNode(`${entryActionText} (${entryVotesText})`); |
|
newEntryEl.appendChild(entryTextNode); |
|
|
|
const entryTimeEl = document.createElement('span'); |
|
entryTimeEl.className = 'vote-history-entry__time'; |
|
entryTimeEl.innerText = new Date().toLocaleTimeString(); |
|
newEntryEl.appendChild(entryTimeEl); |
|
|
|
while (state.dom.voteHistoryContainerEl.childElementCount >= MAX_ENTRIES) { |
|
state.dom.voteHistoryContainerEl.lastElementChild.remove(); |
|
} |
|
|
|
if (state.dom.voteHistoryContainerEl.childElementCount > 0) { |
|
state.dom.voteHistoryContainerEl.insertBefore(newEntryEl, state.dom.voteHistoryContainerEl.firstChild); |
|
} else { |
|
state.dom.voteHistoryContainerEl.appendChild(newEntryEl); |
|
} |
|
} |
|
|
|
|
|
{ |
|
const waitForVueInterval = setInterval(() => { |
|
const vue = containerEl.__vue__; |
|
if (!vue?.changeStop) { |
|
return; |
|
} |
|
|
|
clearInterval(waitForVueInterval); |
|
start(vue); |
|
}, 100); |
|
} |
|
})(); |