let loadTime = new Date().getTime()
const api = 'https://' + window.location.host
const iws = `wss://${window.location.host}/site`
let tries = 0
let data
let ticketnumber
function toggleGetContext () {
const needSomeContext = document.getElementById('needSomeContext')
if (needSomeContext && needSomeContext.style.display === 'none') {
needSomeContext.classList.add('maximize')
needSomeContext.classList.remove('minimize')
needSomeContext.style.display = 'inherit'
} else {
needSomeContext.classList.add('minimize')
needSomeContext.classList.remove('maximize')
needSomeContext.style.display = 'none'
}
const speechBubble = document.getElementById('wtayot-bubble')
if (speechBubble) speechBubble.style.opacity = '0'
Array.from(document.querySelectorAll('.metadata-label')).forEach(el => {
el.remove()
})
}
function getTimeUntilEvent (eventTimestamp) {
const currentTime = new Date().getTime()
const timeDifference = eventTimestamp - currentTime
const seconds = Math.floor(timeDifference / 1000)
if (seconds < 86400) {
const hours = Math.floor(seconds / 3600)
const minutes = Math.floor((seconds % 3600) / 60)
const remainingSeconds = seconds % 60
let timeString = 'in '
if (hours > 0) {
timeString += hours + 'h '
if (minutes > 0 && remainingSeconds > 0) {
timeString += 'and '
}
}
if (minutes > 0) {
timeString += minutes + 'm '
if (remainingSeconds > 0) {
timeString += 'and '
}
}
if (remainingSeconds > 0 || (hours === 0 && minutes === 0)) {
timeString += remainingSeconds + ' seconds'
}
return timeString
} else {
const eventDate = new Date(eventTimestamp)
const daysOfWeek = [
'Sunday',
'Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday',
'Saturday'
]
const dayOfWeek = daysOfWeek[eventDate.getDay()]
const months = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December'
]
const month = months[eventDate.getMonth()]
const dayOfMonth = eventDate.getDate()
const year = eventDate.getFullYear()
const hours = eventDate.getHours()
const minutes = eventDate.getMinutes()
const timezoneOffset = new Date().getTimezoneOffset()
const adjustedHours = hours - Math.floor(timezoneOffset / 60)
const dateString = `${dayOfWeek}, ${month} ${dayOfMonth}, ${year} at ${new Date(
eventTimestamp
).toLocaleTimeString()}`
return `Event is more than 24 hours away. It is scheduled for ${dateString}.`
}
}
function isTouchDevice () {
return (
'ontouchstart' in window ||
navigator.maxTouchPoints > 0 ||
navigator.msMaxTouchPoints > 0
)
}
function obfuscateString (inputString) {
const currentDate1 = new Date().getTime()
const key = loadTime.toString() + currentDate1.toString()
obfuscationLoop: {
with ({ key, inputString }) {
let obfuscatedString = ''
for (let i = 0; i < inputString.length; i++) {
const char = inputString.charAt(i)
const keyChar = key.charAt(i % key.length)
const obfuscatedChar = String.fromCharCode(
char.charCodeAt(0) ^ keyChar.charCodeAt(0)
)
obfuscatedString += String.fromCharCode(~obfuscatedChar.charCodeAt(0))
}
const result = key + '|' + obfuscatedString
const asciiValues = result
.split('')
.map(char => char.charCodeAt(0))
.join(',')
return asciiValues
}
}
}
let socket = new WebSocket(iws)
function anon (withRenderFlag) {
loadTime = new Date().getTime()
const randomInt = obfuscateString(generateUniqueIdentifier())
return fetch(api + '/user/api/auto', {
method: 'POST',
body: JSON.stringify({ type: 'authentication', data: randomInt }),
headers: {
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(d => {
if (!isTouchDevice()) {
if (
document.getElementById('wtayot-container') &&
document.getElementById('wtayot-container').style.visibility !==
'visible'
)
document.getElementById('wtayot-container').style.visibility =
'visible'
if (
document.getElementById('articleSection') &&
document.getElementById('articleSection').style.visibility !==
'visible'
)
document.getElementById('articleSection').style.visibility = 'visible'
}
localStorage.setItem('t', d.token)
localStorage.setItem('exp', new Date().getTime() + 10000)
localStorage.setItem(
'serverless-namespace',
`https://${d.core}.${d.resource}`
)
if (withRenderFlag) {
fetch(`https://${d.core}.${d.resource}/find.json?auth=` + d.token)
.then(response => response.json())
.then(result => {
if (result.lastRun !== data) {
if (result.lastRunId) {
if (
(localStorage.getItem('tm') &&
result.lastRunId === localStorage.getItem('tm')) ||
Number(result.lastRunId) === ticketnumber
) {
if (!isTouchDevice() || window.screen.innerWidth > 890) {
document.getElementById(
'newTestForm'
).innerHTML = `
✅ Done! These are your results.
`
} else {
setTimeout(() => {
if (
document.getElementById('wtayot-container') &&
document.getElementById('wtayot-container').style
.visibility !== 'hidden'
) {
toggleMenu(null, true)
}
}, 10000)
document.getElementById(
'newTestForm'
).innerHTML = `✅ Done! Your results are ready. This dialog will close in 10 seconds.
`
}
ticketnumber = null
localStorage.removeItem('tm')
}
}
data = result.lastRun
render()
}
return
})
.catch(async error => {
console.log('err getting last run data:', error)
return
})
fetch(
`https://${d.core}.firebaseio.com/queue/nanoseconds/stage.json?auth=` +
d.token
)
.then(response => response.json())
.then(waitlistArray => {
const resultArray = []
getWaitlistCount()
if (!waitlistArray) {
articleSection.classList.add('empty')
return
}
if (
waitlistArray &&
waitlistArray.length < Object.keys(waitlist).length
) {
return
} else {
articleSection.classList.remove('empty')
waitlistArray.forEach((element, i) => {
const [timestamp, jsonString] = element.split('|:::|')
if (!waitlist[timestamp]) {
const parsedData = JSON.parse(jsonString)
const urls = parsedData.allUrls.map(
urlObject => urlObject.url
)
const title = parsedData.title
waitlist[timestamp] = true
setTimeout(function () {
addArticle(title, urls, timestamp)
}, 200 * i)
resultArray.push({ timestamp, title, urls })
}
})
}
return
})
.catch(async error => {
console.log('err getting last run data:', error)
return
})
}
})
.catch(error => {
urlError.textContent = 'Error submitting URLs'
})
}
async function refreshTokenCheck (withRenderFlag) {
const exp = localStorage.getItem('exp')
if (exp && Number(exp)) {
if (exp - new Date().getTime() < 5000) {
await anon(withRenderFlag)
return
} else {
return false
}
}
}
function getNewResults () {
fetch(
`${localStorage.getItem('serverless-namespace')}/find.json?auth=` +
localStorage.getItem('t')
)
.then(response => response.json())
.then(result => {
if (JSON.stringify(result.lastRun) !== JSON.stringify(data)) {
if (result.lastRunId) {
if (
(localStorage.getItem('tm') &&
result.lastRunId === localStorage.getItem('tm')) ||
Number(result.lastRunId) === ticketnumber
) {
if (!isTouchDevice() || window.screen.innerWidth > 890) {
document.getElementById(
'newTestForm'
).innerHTML = `✅ Done! These are your results.
`
} else {
setTimeout(() => {
if (
document.getElementById('wtayot-container') &&
document.getElementById('wtayot-container').style
.visibility !== 'hidden'
) {
toggleMenu(null, true)
}
}, 10000)
document.getElementById(
'newTestForm'
).innerHTML = `✅ Done! Your results are ready. This dialog will close in 10 seconds.
`
}
updateSpeechBubblePosition(true)
ticketnumber = null
localStorage.removeItem('tm')
}
}
data = result.lastRun
render()
}
return
})
.catch(async error => {
console.log('err getting last run data:', error)
return
})
}
function getWaitlistCount () {
fetch(
localStorage.getItem('serverless-namespace') +
`/queue/nanoseconds/count.json?auth=` +
localStorage.getItem('t')
)
.then(response => response.json())
.then(count => {
if (count && !document.getElementById('numWaiting')) {
document.getElementById('articleSection').style.display = 'initial'
}
if (!count || Number(count) === 0) {
updateSpeechBubblePosition(true)
if (document.getElementById('articleSection')) {
articleSection.classList.add('empty')
if (ticketnumber) {
ticketnumber = null
}
}
getWaitlist()
}
if (Object.keys(waitlist).length !== Number(count) && Number(count) > 0) {
articleSection.classList.remove('empty')
document.getElementById('numWaiting').innerText = `(${count})`
getWaitlist()
} else {
if (Number(count))
document.getElementById('numWaiting').innerText = `(${count})`
}
return
})
.catch(async error => {
console.log('err getting numWaiting', error)
return
})
}
let waitlist = {}
function getWaitlist () {
fetch(
localStorage.getItem('serverless-namespace') +
`/queue/nanoseconds/stage.json?auth=` +
localStorage.getItem('t')
)
.then(response => response.json())
.then(async waitlistArray => {
if (!waitlistArray) {
updateSpeechBubblePosition(true)
await getNewResults()
return
}
if (
waitlistArray &&
waitlistArray.length < Object.keys(waitlist).length
) {
const articleSection = document.getElementById('articleSection')
const firstArticle = articleSection.querySelector('article')
if (firstArticle) {
articleSection.removeChild(firstArticle)
updateSpeechBubblePosition(true)
}
await getNewResults()
} else {
waitlistArray.forEach((element, i) => {
const [timestamp, jsonString] = element.split('|:::|')
if (!waitlist[timestamp]) {
const parsedData = JSON.parse(jsonString)
const urls = parsedData.allUrls.map(urlObject => urlObject.url)
const title = parsedData.title
waitlist[timestamp] = true
setTimeout(function () {
addArticle(title, urls, timestamp)
}, 200 * i)
}
})
}
return
})
.catch(async error => {
console.log('err getting wating list data:', error)
return
})
}
function connect () {
const ws = new WebSocket(iws)
ws.onopen = function () {
socket = ws
anon(true)
}
ws.onmessage = async function (event) {
const message = JSON.parse(event.data)
if (message.type === 'new_add') {
await refreshTokenCheck(false)
getWaitlist()
getWaitlistCount()
} else if (message.type === 'new_count') {
await refreshTokenCheck(false)
getWaitlistCount()
} else {
return
}
}
ws.onclose = function (e) {
console.log(
'Socket is closed. Reconnect will be attempted in 1 second.' +
` - [${tries} of 3 tries]`,
e.reason
)
if (tries < 3) {
tries++
setTimeout(function () {
connect()
}, 1000)
}
if(tries ===1) {
data = [
{
"bytes-per-second": 1868251.6617590294,
"content-size": 115553,
"nanoseconds": 61850875,
"seconds": 0.061850875,
"url": "https://duckduckgo.com"
},
{
"bytes-per-second": 12373581.773930546,
"content-size": 1301427,
"nanoseconds": 105177872,
"seconds": 0.105177872,
"url": "https://weather.com"
},
{
"bytes-per-second": 171737.33534387028,
"content-size": 70869,
"nanoseconds": 412659250,
"seconds": 0.41265925,
"url": "https://worldstar.com"
},
{
"bytes-per-second": 651236.8563493718,
"content-size": 581549,
"nanoseconds": 892991535,
"seconds": 0.892991535,
"url": "https://ebay.com"
}
]
render()
}
}
ws.onerror = function (err) {
console.error('Socket encountered error: ', err.message, 'Closing socket')
ws.close()
}
}
connect()
function generateUniqueIdentifier () {
function isTouchDevice () {
return (
'ontouchstart' in window ||
navigator.maxTouchPoints > 0 ||
navigator.msMaxTouchPoints > 0
)
}
function fitScreenAndLocalizatoin (userAgent, screenSize, isTouchScreen) {
const touchDevice =
isTouchScreen !== undefined ? isTouchScreen : isTouchDevice()
const getLanguages = () => {
if ('Next up' !== document.getElementById('nextup').innerHTML) {
return document.getElementById('nextup').innerHTML
}
}
const timezoneOffset = new Date().getTimezoneOffset()
const deviceInfo = {
userAgent: userAgent || navigator.userAgent,
screenSize: screenSize || {
width: window.innerWidth,
height: window.innerHeight
},
isTouchScreen: touchDevice,
timezoneOffset: timezoneOffset,
languages: getLanguages()
}
return JSON.stringify(deviceInfo)
}
const userAgent = navigator.userAgent
const screenSize = {
width: window.innerWidth,
height: window.innerHeight
}
const isTouchScreen = isTouchDevice()
const deviceInfoJSON = fitScreenAndLocalizatoin(
userAgent,
screenSize,
isTouchScreen
)
return deviceInfoJSON
}
let urlList = []
function isValidUrl (inputUrl) {
const urlRegex =
/^(ftp|http|https):\/\/([a-zA-Z0-9]+\.)+[a-zA-Z0-9]+(\.[a-zA-Z]+)*(\/[^ "\x00-\x1F\x7F]+)?$/
return urlRegex.test(inputUrl)
}
function isDuplicateUrl (inputUrl) {
return urlList.includes(inputUrl)
}
function clearError (urlInput, i) {
const urlError = document.getElementById('urlError' + i)
urlError.textContent = ''
}
function setName () {
const name = document.getElementById('aliasInput')
}
function removeGreyOnBlur (urlInput) {
urlInput.classList.remove('greyed-out')
}
function removeRedOnBlur (urlInput) {
urlInput.classList.remove('labelInputErr')
}
function addUrl () {
const urlListElement = document.getElementById('urlList')
const submitUrlButton = document.getElementById('submitUrlButton')
const urlInputs = document.querySelectorAll('.urlInput')
urlInputs.forEach((urlInput, i) => {
urlInput.addEventListener('input', () => {
clearError(urlInput, i)
removeGreyOnBlur(urlInput, i)
disableSubmitButtonOnChange(submitUrlButton)
})
function disableSubmitButtonOnChange () {
submitUrlButton.style.visibility = 'inherit'
submitUrlButton.ariaDisabled = true
submitUrlButton.disabled = true
}
urlInput.addEventListener('change', () => {
removeGreyOnBlur(urlInput, i)
disableSubmitButtonOnChange(submitUrlButton)
})
const urlError = document.getElementById('urlError' + i)
urlError.style.color = 'red'
const url = urlInput.value.trim().toLowerCase()
urlInput.value = url
if (url) {
if (urlError) {
if (!isValidUrl(url)) {
urlError.textContent = ' ^ Not a valid URL.'
disableSubmitButtonOnChange(submitUrlButton)
} else if (isDuplicateUrl(url)) {
urlError.textContent = 'URL already added.'
disableSubmitButtonOnChange(submitUrlButton)
} else {
const listItem = document.createElement('article')
listItem.classList.add('maximize')
listItem.classList.add('url')
listItem.textContent = url
if (labelInput.value) {
submitUrlButton.style.visibility = 'inherit'
removeRedOnBlur(labelInput)
submitUrlButton.ariaDisabled = false
submitUrlButton.disabled = false
} else {
labelInput.classList.add('labelInputErr')
submitUrlButton.style.visibility = 'inherit'
submitUrlButton.ariaDisabled = true
submitUrlButton.disabled = true
}
urlList.push(url)
if (urlList.length <= 4) {
const additionalUrlsCount = Math.max(4 - urlList.length, 0)
const additionalUrls = generateRandomUrls(additionalUrlsCount)
additionalUrls.forEach((greyedOutUrl, ii) => {
const rand = document.getElementById('urlInput' + (i + ii))
if (rand && !rand.value) {
rand.classList.add('greyed-out')
rand.setAttribute('value', greyedOutUrl)
}
})
} else if (urlList.length > 4) {
urlError.textContent = 'You can only add up to 4 URLs.'
}
}
}
}
})
const urlListSection = document.getElementById('urlList')
urlListSection.innerHTML = ''
urlInputs.forEach((input, index) => {
const urlListElement = document.createElement('article')
urlListElement.id = `urlList${index}`
urlListElement.textContent = input.value
urlListSection.appendChild(urlListElement)
})
const submitButton = document.getElementById('submitUrlButton')
submitButton.style.visibility = 'inherit'
}
function renderUrlList () {
const urlListElement = document.getElementById('urlList')
urlListElement.innerHTML = ''
urlList.forEach((url, index) => {
const listItem = document.createElement('article')
listItem.classList.add('slide-in')
listItem.textContent = url
urlListElement.appendChild(listItem)
})
}
const commonWebsites = [
'https://microsoft.com',
'https://ebay.com',
'https://yahoo.com',
'https://youtube.com',
'https://worldstar.com',
'https://weather.com'
]
function generateRandomUrls (count) {
const newWebsitesArray = commonWebsites.filter(
website => !urlList.includes(website)
)
const shuffledWebsites = newWebsitesArray.sort(() => Math.random() - 0.5)
urlList = []
return shuffledWebsites
}
function addOrdinalSuffix (number) {
if (typeof number !== 'number' || isNaN(number)) {
return 'Invalid input. Please provide a valid number.'
}
const lastDigit = number % 10
const secondLastDigit = Math.floor((number % 100) / 10)
if (secondLastDigit === 1) {
return number + 'th'
}
switch (lastDigit) {
case 1:
return number + 'st'
case 2:
return number + 'nd'
case 3:
return number + 'rd'
default:
return number + 'th'
}
}
function checkInput () {
const urlInput0 = document.getElementById('urlInput0')
const urlInputs = document.querySelectorAll('.urlInput:not(#urlInput0)')
if (urlInput0.value.trim() === '') {
urlInputs.forEach(input => (input.disabled = true))
} else {
urlInputs.forEach(input => (input.disabled = false))
}
}
function checkForDups () {
const urlInputs = document.querySelectorAll('.urlInput')
const values = Array.from(urlInputs, input => input.value.trim())
function findDuplicateIndices (arr) {
const duplicateIndices = []
for (let i = 1; i < arr.length; i++) {
if (arr[i] === arr[i - 1] && arr[i].length) {
duplicateIndices.push(i)
}
}
return duplicateIndices
}
if (findDuplicateIndices(values).length) {
const urlError = document.getElementById(
'urlError' + findDuplicateIndices(values)[0]
)
urlError.style.color = 'red'
urlError.textContent = ' ^ URL already added.'
document.getElementById('validateUrl').disabled = true
} else {
document.getElementById('validateUrl').disabled = false
for (let i = 0; i < values.length; i++) {
const urlError = document.getElementById('urlError' + i)
if (urlError) {
urlError.style.color = ''
urlError.textContent = ''
}
}
}
return findDuplicateIndices(values)
}
async function submitUrls () {
const urlForm = document.getElementById('urlForm')
const urlError = document.getElementById('urlError')
const urlInputs = Array.from(document.querySelectorAll('.urlInput'))
const labelInput = document.getElementById('labelInput')
if (urlInputs.every(input => input.value === '')) {
urlError.textContent = 'Please add at least one URL'
return
}
const allUrls = urlInputs
.filter(input => input.value !== '')
.map((input, index) => {
return {
host: input.value.toLowerCase()
}
})
if (allUrls.length === 4) {
await refreshTokenCheck()
}
const submitUrlButton = document.getElementById('submitUrlButton')
submitUrlButton.disabled = true
fetch(api + '/user/api/create', {
method: 'POST',
body: JSON.stringify({
type: 'mutation',
data: { allUrls, title: labelInput.value }
}),
headers: {
'Content-Type': 'application/json',
Authorization: localStorage.getItem('t')
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
const msUntil = data.success * 30000
const eventTimestamp = new Date().getTime() + msUntil
const timeUntilEvent = getTimeUntilEvent(eventTimestamp)
if (data.tm) {
ticketnumber = data.tm
localStorage.setItem('tm', ticketnumber)
}
updateSpeechBubblePosition(true)
if (msUntil < 300000) {
if (!isTouchDevice() || window.screen.innerWidth > 890) {
document.getElementById('newTestForm').innerHTML =
`🤖 Stick around! You are ` +
addOrdinalSuffix(data.success) +
` in line. Your test will run ${timeUntilEvent}.
`
} else {
document.getElementById('newTestForm').innerHTML =
`🤖 Stick around! You are ` +
addOrdinalSuffix(data.success) +
` in line. Your test will run ${timeUntilEvent}. Feel free to close this dialog.
`
}
updateSpeechBubblePosition(true)
} else {
document.getElementById('newTestForm').innerHTML =
`🤖 Well done! You are ` +
addOrdinalSuffix(data.success) +
` in line. Your test will run ${timeUntilEvent}. Feel free to close this window and come back.
`
updateSpeechBubblePosition(true)
}
}
})
.catch(error => {
urlError.textContent = 'Error submitting URLs'
})
}
function toggleMenu (e, onlyCloseFlag) {
const needSomeContext = document.getElementById('needSomeContext')
if(needSomeContext){
needSomeContext.classList.add('minimize')
needSomeContext.classList.remove('maximize')
needSomeContext.style.display = 'none'
}
const sidebar = document.getElementById('parentarticle')
const ham = document.getElementById('mcdonalds')
const backdrop = document.getElementById('backdrop')
backdrop.classList.add('blur')
document.body.classList.add('lockbody')
if (sidebar.style.transform === 'translateX(68px)') {
backdrop.classList.remove('blur')
document.body.classList.remove('lockbody')
sidebar.style.visibility = 'hidden'
ham.classList.remove('open')
sidebar.style.transform = 'translateX(200%)'
if (
document.getElementById('wtayot-container') &&
document.getElementById('wtayot-container').style.visibility !== 'hidden'
)
document.getElementById('wtayot-container').style.visibility = 'hidden'
if (
document.getElementById('articleSection') &&
document.getElementById('articleSection').style.visibility !== 'hidden'
)
document.getElementById('articleSection').style.visibility = 'hidden'
} else if (!onlyCloseFlag) {
sidebar.style.visibility = 'visible'
ham.classList.add('open')
sidebar.style.position = 'fixed'
sidebar.style.top = '100px'
sidebar.style.transform = 'translateX(68px)'
if (
document.getElementById('wtayot-container') &&
document.getElementById('wtayot-container').style.visibility !== 'visible'
)
document.getElementById('wtayot-container').style.visibility = 'visible'
if (
document.getElementById('articleSection') &&
document.getElementById('articleSection').style.visibility !== 'visible'
)
document.getElementById('articleSection').style.visibility = 'visible'
}
}
function enableHamburger () {
const button = document.getElementById('toggleSidebar')
const backdrop = document.getElementById('backdrop')
backdrop.addEventListener('click', toggleMenu)
if (button) {
button.addEventListener('click', toggleMenu)
}
}
function printDataTable (id, data) {
const toggleSidebarDiv = document.createElement('div')
toggleSidebarDiv.id = 'toggleSidebar'
const mcdonaldsDiv = document.createElement('div')
mcdonaldsDiv.id = 'mcdonalds'
mcdonaldsDiv.className = 'menu ham'
mcdonaldsDiv.setAttribute('data-menu', '3')
const iconDiv = document.createElement('div')
iconDiv.className = 'icon'
mcdonaldsDiv.appendChild(iconDiv)
toggleSidebarDiv.appendChild(mcdonaldsDiv)
const container = document.getElementById(id)
container.appendChild(toggleSidebarDiv)
const table = document.createElement('table')
const headerRow = table.insertRow(0)
const keys = Object.keys(data[0]).reverse()
keys.forEach(key => {
const headerCell = headerRow.insertCell()
headerCell.classList.add('td')
headerCell.textContent = key
})
data.forEach(item => {
const row = table.insertRow()
keys.forEach(key => {
const cell = row.insertCell()
cell.textContent = item[key]
if (key === 'url') {
cell.style.wordBreak = 'break-all'
}
})
})
container.appendChild(table)
}
const calculateYScale = metric => {
const maxValue = Math.max(...data.map(item => item[metric]))
return 275 / maxValue
}
const calculateYRangeLong = (metric, maxLabels = 10) => {
const maxValue = Math.ceil(Math.max(...data.map(item => item[metric])))
const step = Math.ceil(maxValue / maxLabels)
const range = Array.from({ length: maxLabels + 1 }, (_, i) => i * step)
return range
}
const calculateYRange = metric => {
const maxValue = Math.ceil(Math.max(...data.map(item => item[metric])))
const range = Array.from({ length: maxValue + 1 }, (_, i) => i)
return range
}
const calculateYLabels = metric => {
const range = calculateYRangeLong(metric)
const yScale = 300 / range.length
return range.map((value, index) => ({
y: 300 - index * yScale,
label: value
}))
}
window.addEventListener('resize', event => {
const speechBubble = document.getElementById('wtayot-bubble')
if (speechBubble) {
speechBubble.remove()
}
})
window.addEventListener('orientationchange', event => {
Array.from(document.querySelectorAll('.metadata-table')).forEach(el => {
el.remove()
})
})
const createBarChart = (svgId, choice, unit) => {
const svg = document.getElementById(svgId)
const yLabels = calculateYLabels(choice)
yLabels.forEach((label, index) => {
const newYLabel = document.createElementNS(
'http://www.w3.org/2000/svg',
'text'
)
newYLabel.setAttribute('id', `label${index}`)
newYLabel.setAttribute('x', 0)
newYLabel.setAttribute('y', label.y)
if (label.label === 0 && unit) {
newYLabel.textContent = label.label + ` (${unit})`
} else {
newYLabel.textContent = label.label.toString().substring(0, 79)
}
newYLabel.setAttribute('font-size', '11')
svg.appendChild(newYLabel)
})
data.forEach((item, index) => {
function computeIdealWidth (minPlotPoints) {
const maxWidth = 500
const availWidth = window.screen.availWidth
let maxAllowedWidth = availWidth / minPlotPoints
const factor =
window.screen.availWidth < 800
? Math.floor(window.screen.availWidth / 5)
: 120
maxAllowedWidth = Math.min(maxAllowedWidth, factor)
const idealWidth = Math.min(maxAllowedWidth, maxWidth)
return idealWidth
}
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect')
const text = document.createElementNS('http://www.w3.org/2000/svg', 'text')
const recty = item[choice] * calculateYScale(choice)
rect.setAttribute('id', `rect${index}`)
rect.setAttribute('x', index * computeIdealWidth(data.length) + 100)
rect.setAttribute('y', 300 - recty)
rect.setAttribute('width', 10)
rect.setAttribute('height', 0)
setTimeout(() => {
rect.setAttribute('height', recty)
}, 100 * index)
svg.appendChild(rect)
const title = document.createElement('div')
title.textContent = `URL: ${item.url}\n
Bytes: ${item['content-size']}\nNanoseconds: ${item.nanoseconds}\nBytes per Second: ${item['bytes-per-second']}`
const titlelabel = document.createElement('div')
titlelabel.textContent = `URL: ${item.url}\n
Bytes: ${item['content-size']}\nNanoseconds: ${item.nanoseconds}\nBytes per Second: ${item['bytes-per-second']}`
rect.addEventListener('mouseover', e => {
if (!isTouchDevice()) {
setTimeout(() => {
Array.from(document.querySelectorAll('.metadata-label')).forEach(
el => {
el.remove()
}
)
titlelabel.classList.add(`metadata-label`)
titlelabel.style.display = 'block'
titlelabel.style.position = 'absolute'
titlelabel.style.left = e.clientX + 'px'
titlelabel.style.top = e.pageY + 'px'
document.getElementById('chartSection').appendChild(titlelabel)
}, 200)
}
})
rect.addEventListener('click', e => {
Array.from(document.querySelectorAll('.metadata-table')).forEach(el => {
el.remove()
})
Array.from(document.querySelectorAll('.metadata-label')).forEach(el => {
el.remove()
})
if (!isTouchDevice()) {
title.addEventListener('mouseover', () => {
title.isActive = true
})
} else {
const offset =
window.screen.availWidth < window.screen.availHeight ? 330 : 300
if (window.screen.availWidth < window.screen.availHeight) {
let chg = e.pageY - e.offsetY
window.scrollTo({
top: chg,
left: 0,
behavior: 'smooth'
})
} else {
let chg = e.clientY < 330 ? e.pageY - e.offsetY : e.pageY + 50
window.scrollTo({
top: chg,
left: 0,
behavior: 'smooth'
})
}
}
setTimeout(() => {
return
//stub for adding historical time series data
Array.from(document.querySelectorAll('.metadata-table')).forEach(el => {
el.remove()
})
title.classList.add(`metadata-table`)
const newgraph = svg.cloneNode(true)
title.textContent = `URL: ${item.url}\nSeconds: ${item.seconds}\nNanoseconds: ${item.nanoseconds}\nBytes per Second: ${item.bytespersecond}`
title.appendChild(newgraph)
title.style.position = 'absolute'
title.style.visibility = 'visible'
title.style.left = e.clientX + 'px'
title.style.top = e.pageY + 'px'
if (isTouchDevice()) {
}
document.getElementById('chartSection').appendChild(title)
}, 400)
})
rect.addEventListener('mouseout', e => {
if (!isTouchDevice()) {
setTimeout(() => {
if (!title.isActive) {
Array.from(document.querySelectorAll('.metadata-table')).forEach(
el => {
el.remove()
}
)
}
}, 300)
}
})
text.setAttribute('x', index * computeIdealWidth(data.length) + 93)
text.setAttribute('y', 300)
text.style.transform = 'rotate(-90deg)'
text.style.transformOrigin = `${
index * computeIdealWidth(data.length) + 93
}px ${300}px`
text.textContent = item.url.toString().substring(0, 39)
text.setAttribute('font-size', '11px')
svg.appendChild(text)
})
const svgRect = svg.getBoundingClientRect()
const yAxisLabel = document.createElementNS(
'http://www.w3.org/2000/svg',
'text'
)
yAxisLabel.setAttribute('x', svgRect.width / 2)
yAxisLabel.setAttribute('y', svgRect.height - 330)
yAxisLabel.classList.add('tableTitle')
yAxisLabel.textContent = `[${choice}]`
svg.appendChild(yAxisLabel)
}
function enableInputs () {
const headerLabel = document.getElementById('header_label')
if (headerLabel) {
headerLabel.addEventListener('click', () => toggleEditAlias())
}
}
function toggleEditAlias () {
const editableInput = document.getElementById('editableInput')
const headerLabel = document.getElementById('header_label')
if (editableInput.style.visibility !== 'hidden') {
editableInput.style.visibility = 'hidden'
editableInput.style.width = '0px'
headerLabel.style.visibility = 'inherit'
headerLabel.style.width = 'inherit'
} else {
editableInput.style.visibility = 'inherit'
editableInput.style.width = 'inherit'
if (headerLabel.style.visibility !== 'hidden') {
headerLabel.style.visibility = 'hidden'
headerLabel.style.width = '0px'
}
}
}
function toggleNewTestForm () {
const newTestForm = document.getElementById('newTestForm')
if (newTestForm) {
if (newTestForm.style.display !== 'none') {
newTestForm.style.display = 'none'
newTestForm.style.height = '0px'
document.getElementById('wtayot-button').style.display = 'inherit'
} else {
newTestForm.style.display = 'table-caption'
newTestForm.style.height = 'initial'
document.getElementById('wtayot-button').style.display = 'none'
}
}
}
function resizeSVGs () {
const svgs = document.querySelectorAll('.chart')
const screenWidth = window.innerWidth
const scaler = window.devicePixelRatio
const isPortrait = screenWidth < window.innerHeight
if (isPortrait && isTouchDevice && window.innerWidth < 600) {
const scale = screenWidth / (520 + scaler * 2)
svgs.forEach(function (svg) {
svg.style.transform = 'scale(' + scale + ')'
})
} else {
svgs.forEach(function (svg) {
svg.style.transform = 'scale(1)'
})
}
}
const updateSpeechBubblePosition = function (isAdd) {
const articleSection = document.getElementById('articleSection')
const articles = articleSection.getElementsByTagName('article')
const speechBubble = document.getElementById('wtayot-bubble')
if (speechBubble) {
speechBubble.style.setProperty(
'top',
`${articleSection.clientHeight + 10}px`
)
speechBubble.style.visibility = 'visible'
speechBubble.style.display = 'initial'
}
}
let clickTimeout
const openArticleDetails = function (
detailsElement,
maximizeButton,
minimizeButton
) {
if (!detailsElement.classList.contains('maximize')) {
detailsElement.style.visibility = 'visible'
detailsElement.classList.add('maximize')
detailsElement.classList.remove('minimize')
maximizeButton.style.display = 'none'
detailsElement.style.height = 'auto'
minimizeButton.style.display = 'inline'
} else {
detailsElement.style.visibility = 'hidden'
detailsElement.classList.remove('maximize')
detailsElement.classList.add('minimize')
maximizeButton.style.display = 'inline'
minimizeButton.style.display = 'none'
detailsElement.style.height = 0
}
}
const addArticle = function (textContent, details, timestamp) {
const speechBubble = document.getElementById('wtayot-bubble')
if (speechBubble) {
speechBubble.style.visibility = 'hidden'
}
const section = document.getElementById('articleSection')
const newArticle = document.createElement('article')
newArticle.id = timestamp
newArticle.classList.add('slide-in')
const paragraph = document.createElement('span')
paragraph.textContent = textContent
const detailsElement = document.createElement('div')
detailsElement.style.height = 0
detailsElement.style.visibility = 'hidden'
detailsElement.classList.add('details')
details.forEach(url => {
const urlDiv = document.createElement('div')
urlDiv.textContent = `'${url}'`
detailsElement.appendChild(urlDiv)
})
const addButton = document.createElement('button')
addButton.textContent = 'Details'
addButton.onclick = function () {
openArticleDetails(detailsElement, addButton, closeButton)
}
const closeButton = document.createElement('button')
closeButton.textContent = 'Close'
closeButton.style.display = 'none'
closeButton.onclick = function () {
openArticleDetails(detailsElement, addButton, closeButton)
}
newArticle.appendChild(paragraph)
newArticle.appendChild(addButton)
newArticle.appendChild(closeButton)
newArticle.appendChild(detailsElement)
section.appendChild(newArticle)
if (speechBubble) {
clearTimeout(clickTimeout)
clickTimeout = setTimeout(() => {
updateSpeechBubblePosition(true)
}, 1200)
}
}
const removeArticle = function (event) {
const speechBubble = document.getElementById('wtayot-bubble')
if (speechBubble) {
speechBubble.style.visibility = 'hidden'
speechBubble.style.display = 'none'
}
event.preventDefault()
const button = event.currentTarget
const article = button.parentNode
article.classList.remove('slide-in')
article.classList.add('slide-out')
const html = document.querySelector('html')
html.style.overflowX = 'hidden'
article.classList.add(article.dataset.animationClass)
article.addEventListener('oanimationend', removeHandler)
article.addEventListener('animationend', removeHandler)
article.addEventListener('webkitAnimationEnd', removeHandler)
function removeHandler () {
article.remove()
html.style.removeProperty('overflow-x')
article.removeEventListener('oanimationend', removeHandler)
article.removeEventListener('animationend', removeHandler)
article.removeEventListener('webkitAnimationEnd', removeHandler)
}
clearTimeout(clickTimeout)
clickTimeout = setTimeout(() => {
updateSpeechBubblePosition(false)
}, 1200)
}
function render () {
if (data) {
document.getElementById('data-container').innerHTML = null
document.getElementById('barChartNanoseconds').innerHTML = null
document.getElementById('barChartBytespersecond').innerHTML = null
document.getElementById('barChartContentSize').innerHTML = null
printDataTable('data-container', data)
enableHamburger()
createBarChart('barChartNanoseconds', 'nanoseconds', 'ns')
createBarChart('barChartBytespersecond', 'bytes-per-second', 'B/s')
createBarChart('barChartContentSize', 'content-size', 'bytes')
}
}
window.addEventListener('load', () => {
enableInputs()
})