class queuePanel extends panelObj{ constructor(client, panelDocument){ super(client, "Media Queue", "/panel/queue", panelDocument); //Store releative scale of items in seconds, defaulting to 30 minute chunks this.scale = 30 * 60; //Create variable to hold rescale timer this.rescaleTimer = null; //Define non-input event listeners this.defineListeners(); } docSwitch(){ //Get queue div this.queue = this.panelDocument.querySelector('#queue'); //Get queue container this.queueContainer = this.queue.querySelector("#queue-container"); //Get queue marker contianer this.queueMarkerContainer = this.queue.querySelector('#queue-marker-container'); //Get queue layout controller this.queueLayoutController = this.panelDocument.querySelector('#queue-panel-layout-controller'); //Re-acquire time marker this.timeMarker = this.panelDocument.querySelector('#time-marker'); //Render out the queue this.fullRender(); //Setup panel input this.setupInput(); } closer(){ //Clear any remaining timers clearTimeout(this.timeMarkerTimer); } defineListeners(){ this.client.socket.on("queue", (data) => { this.renderQueue(); }) } setupInput(){ this.queue.addEventListener('wheel', this.scaleScroll.bind(this)); } scaleScroll(event){ if(event.ctrlKey){ //Capture inverse scroll wheel direction const scrollDirection = event.wheelDeltaY / Math.abs(event.wheelDeltaY) * -1; //Default scale factor to 5 seconds let scaleFactor = 5; //Tried to do this with math but couldnt because im bad at math so heres the if statement of shame :( if(this.scale >= 7200){ scaleFactor = 3600 }else if(this.scale >= 3600){ scaleFactor = scrollDirection > 0 ? 3600 : 1800; }else if(this.scale >= 1800){ scaleFactor = scrollDirection > 0 ? 1800 : 900; }else if(this.scale == 900){ scaleFactor = scrollDirection > 0 ? 900 : 300; }else if(this.scale > 300){ //If we're above five minutes use five minutes scaleFactor = 300; }else if(this.scale == 300){ //If we're at five minutes scroll up by five minutes or scroll down to one minute scaleFactor = scrollDirection > 0 ? 300 : 240; }else if(this.scale == 60){ //If we're at one minutes scroll up by four minutes or scroll down by 10 seconds scaleFactor = scrollDirection > 0 ? 240 : 10; }else if(this.scale > 10){ scaleFactor = 10; }else if(this.scale == 10){ scaleFactor = scrollDirection > 0 ? 10 : 5; } //Prevent page-wide zoom in/out event.preventDefault(); //Clear out the queue UI this.clearQueue(); //Calculate new scale const newScale = this.scale + (scaleFactor * scrollDirection); //Clamp scale between 10 seconds and half a day this.scale = Math.max(5, Math.min(43200, newScale)); //If we have no scale label if(this.scaleLabel == null){ //Make it this.scaleLabel = document.createElement('p'); this.scaleLabel.id = 'queue-marker-scale-label'; this.queue.appendChild(this.scaleLabel); } //Set scale label text to humie readable time scale this.scaleLabel.innerHTML = `Time Scale:
${this.humieFriendlyDuration(this.scale)}` //Clear any previous rescale timer clearTimeout(this.rescaleTimer); //Set timeout to re-render after input stops this.rescaleTimer = setTimeout(this.fullRender.bind(this), 500); } } humieFriendlyDuration(seconds){ //If we have an invalid duration if(seconds <= 0){ //bitch, moan, and complain! return('Invalid input duration'); } //Create an empty array to hold the time strings const timeStrings = []; //Pull hours from time const hours = Math.floor(seconds / 3600); //Remove recorded hours seconds -= hours * 3600; //Pull minutes from time const minutes = Math.floor(seconds / 60); //Remove recorded minutes seconds -= minutes * 60; //If we have an hour if(hours == 1){ //Add the string timeStrings.push('1 Hour'); //If we have hours }else if(hours > 0){ //Add the string timeStrings.push(`${hours} Hours`); } //If we have a minute if(minutes == 1){ //Add the string timeStrings.push('1 Minute'); //If we have minutes }else if(minutes > 0){ //Add the string timeStrings.push(`${minutes} Minutes`); } //Add the 'and ' if we need it const secondsPrefix = timeStrings.length > 0 ? 'and ' : ''; //If we have a second if(seconds == 1){ //Add the string timeStrings.push(`${secondsPrefix}1 Second`); //If we have more than a second }else if(seconds > 1){ //Add the string timeStrings.push(`${secondsPrefix}${seconds} Seconds`); } //Join the time strings together return timeStrings.join(', '); } clearQueue(){ //Clear out queue container this.queueContainer.innerHTML = ''; //Clear out queue marker container this.queueMarkerContainer.innerHTML = ''; //Clear any marker timers clearTimeout(this.timeMarkerTimer); //If we have an existing time marker if(this.timeMarker != null){ //Clear it out this.timeMarker.remove(); this.timeMarker = null; } } async fullRender(date = new Date()){ //Clear the queue this.clearQueue(); //If we have a scale label if(this.scaleLabel != null){ //Take it out this.scaleLabel.remove(); this.scaleLabel = null; } //Render out time scale this.renderQueueScale(date); //wait a few frames so the scale can finish rendering, because dom function aren't async for some fucking reason for(let i = 0; i <= 2; i++){ await utils.awaitNextFrame(); } //render the time marker this.renderTimeMarker(date); //render out the queue this.renderQueue(date); } renderQueue(date = new Date()){ //for every entry in the queue for(let entry of this.client.queue){ //Create entry div const entryDiv = document.createElement('div'); entryDiv.classList.add('queue-entry'); //Place entry div entryDiv.style.top = `${this.offsetByDate(new Date(entry[1].startTime))}px`; entryDiv.style.bottom = `${this.offsetByDate(new Date(entry[1].startTime + (entry[1].duration * 1000)), true)}px`; //Create entry title const entryTitle = document.createElement('a'); entryTitle.textContent = entry[1].title; entryTitle.href = entry[1].url; //Append entry elements entryDiv.append(entryTitle); //Append entry this.queueContainer.append(entryDiv); } } renderTimeMarker(date = new Date()){ //Calculate marker position by date const markerPosition = Math.round(this.offsetByDate(date)); //If markers are null if(markerPosition == null){ //Try again in a second since the user probably just popped the panel or something :P (smackTimer.bind(this))(); } //if we need to make the time marker if(this.timeMarker == null){ //Create the time marker this.timeMarker = document.createElement('span'); //Add time marker class this.timeMarker.id = 'time-marker'; //Append time marker this.queue.appendChild(this.timeMarker); } //Set time marker position this.timeMarker.style.top = `${markerPosition}px`; //If the panel document isn't null (we're not actively switching panels) if(this.panelDocument != null){ //Grab the current owner document object const ownerDoc = this.panelDocument.ownerDocument == null ? this.panelDocument : this.panelDocument.ownerDocument; //Get height difference between window and queue layout controller const docDifference = ownerDoc.defaultView.innerHeight - this.queueLayoutController.getBoundingClientRect().height; //Calculate scroll target by body difference and marker position const scrollTarget = (markerPosition - (this.queueLayoutController.getBoundingClientRect().height - docDifference) / 2) + docDifference; //Calculate scroll behavior by distance const scrollBehavior = Math.abs(scrollTarget - this.queueLayoutController.scrollTop) > 10 ? "smooth" : "instant"; //Scroll to the marker this.queueLayoutController.scroll({ left: 0, top: scrollTarget, behavior: scrollBehavior }); } //Set the timer to run the function again (smackTimer.bind(this))(); function smackTimer(){ //Clear update timer clearTimeout(this.timeMarkerTimer); //Set timer to update marker every second this.timeMarkerTimer = setTimeout(this.renderTimeMarker.bind(this), 1000); } } renderQueueScale(inputDate = new Date()){ //Make sure we don't modify the date we're handed const date = structuredClone(inputDate); //Zero out time to midnight on the morning of the input date date.setHours(0,0,0,0); //Store epoch of current date at midnight for later user const dateEpoch = date.getTime(); //while we've counted less than the amount of time in the day, count up by scale for(let time = 0; time <= 86400; time += this.scale){ //Get index of current chunk by dividing time by scale const index = time / this.scale; //Set time by scale, we could do this against this.scale and date.getTime(), but this seemed cleaner :P date.setTime(dateEpoch + (time * 1000)) //Create marker span const markerSpan = document.createElement('div'); markerSpan.classList.add('queue-marker'); //Create marker line (unfortunately
tags don't seem to play nice with parents who have display:flex) const marker = document.createElement('span'); marker.classList.add('queue-marker'); //If it's even/zero if(index % 2 == 0){ const markerLabel = document.createElement('p'); //If scale is over a minute then we don't need to display seconds const seconds = this.scale > 60 ? '' : `:${('0' + date.getSeconds()).slice(-2)}` //If we're counting AM if(date.getHours() < 12){ //Display as AM markerLabel.textContent = `${('0'+date.getHours()).slice(-2)}:${('0' + date.getMinutes()).slice(-2)}${seconds}AM` //If we're cointing noon }else if(date.getHours() == 12){ //display as noon markerLabel.textContent = `${('0'+date.getHours()).slice(-2)}:${('0' + date.getMinutes()).slice(-2)}${seconds}PM` //if we're counting pm }else{ //display as pm markerLabel.textContent = `${('0'+(date.getHours() - 12)).slice(-2)}:${('0' + date.getMinutes()).slice(-2)}${seconds}PM` } //Add marker label to marker span markerSpan.appendChild(markerLabel); } //Append marker to marker span markerSpan.appendChild(marker); //Append marker span to queue container this.queueMarkerContainer.appendChild(markerSpan); } } offsetByDate(date = new Date(), bottomOffset = false){ //Pull start of day epoch from given date const dayEpoch = structuredClone(date).setHours(0,0,0,0); //Get difference between now and day epoch to get time since the start of the current day in milliseconds const curTime = date.getTime() - dayEpoch; //Devide by amount of milliseconds in a day to convert time over to a floating point number between 0 and 1 //Make sure to reverse it if bottomOffset is set to true const timeFloat = bottomOffset ? 1 - (curTime / 86400000) : curTime / 86400000; //Get queue markers const markers = this.panelDocument.querySelectorAll('span.queue-marker'); //If the marker is null for some reason if(markers[0] == null){ return null; } //Get marker position range const range = [markers[0].offsetTop, markers[markers.length - 1].offsetTop] //Get maximum position relative to the range itself const offsetMax = range[1] - range[0]; //return position relative to parent return (offsetMax * timeFloat) + range[0]; } }