class queuePanel extends panelObj{ constructor(client, panelDocument){ //Call derived constructor super(client, "Media Schedule", "/panel/queue", panelDocument); //Current day this.day = new Date(); //Zero out day to midnight this.day.setHours(0,0,0,0); //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; //Autoscroll boolean this.autoscroll = true; //Define non-input event listeners this.defineListeners(); } docSwitch(){ //Call derived doc switch function super.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'); //Get queue control offset this.queueControlOffset = this.panelDocument.querySelector('#queue-control-offset'); //Re-acquire time marker this.timeMarker = this.panelDocument.querySelector('#time-marker'); //Dragscroll timer this.dragScrollTimer = null; //Get main control buttons this.addMediaButton = this.panelDocument.querySelector('#queue-add-media'); this.scrollLockButton = this.panelDocument.querySelector('#queue-scroll-lock'); this.queueDateButton = this.panelDocument.querySelector('#queue-date') this.clearMediaButton = this.panelDocument.querySelector('#queue-clear'); this.queueLockButton = this.panelDocument.querySelector('#queue-lock'); //Get control divs this.addMediaDiv = this.panelDocument.querySelector('#queue-media-prompts'); this.queueDateDiv = this.panelDocument.querySelector('#queue-control-date'); //Get control div elements //Add Media this.addMediaLinkPrompt = this.panelDocument.querySelector('#media-link-input'); this.addMediaNamePrompt = this.panelDocument.querySelector('#media-name-input'); this.queueLastButton = this.panelDocument.querySelector('#queue-last-button'); this.queueAtButton = this.panelDocument.querySelector('#queue-at-button'); //Date Queue date this.queueDateDecrement = this.panelDocument.querySelector('#queue-control-date-decrement'); this.queueDateIncrement = this.panelDocument.querySelector('#queue-control-date-increment'); this.queueDatePrompt = this.panelDocument.querySelector('#queue-control-date-prompt'); //Display lock status this.handleScheduleLock(); //Render out the queue this.fullRender(); //Setup panel input this.setupInput(); } closer(){ //Clear any remaining timers clearTimeout(this.timeMarkerTimer); } defineListeners(){ //Render queue when we receive a new copy of the queue data from the server this.client.socket.on("clientMetadata", (data) => {this.renderQueue();}); this.client.socket.on("queue", (data) => {this.renderQueue();}); this.client.socket.on("lock", this.handleScheduleLock.bind(this)); } setupInput(){ //Re-render queue and time-marker on window resize as time-relative absolute positioning will be absolutely thrown this.ownerDoc.defaultView.addEventListener('resize', this.resizeRender.bind(this)); //queue this.queue.addEventListener('wheel', this.scaleScroll.bind(this)); //control bar controls this.addMediaButton.addEventListener('click', this.toggleAddMedia.bind(this)); this.scrollLockButton.addEventListener('click', this.lockScroll.bind(this)); this.queueDateButton.addEventListener('click', this.toggleDateControl.bind(this)); this.clearMediaButton.addEventListener('click', this.clearMedia.bind(this)); this.queueLockButton.addEventListener('click', this.lockSchedule.bind(this)); //control bar divs //Add Media this.queueLastButton.addEventListener('click', this.queueLast.bind(this)) this.queueAtButton.addEventListener('click', this.queueAt.bind(this)) //Queue Date this.queueDateDecrement.addEventListener('click', this.decrementDate.bind(this)); this.queueDateIncrement.addEventListener('click', this.incrementDate.bind(this)); this.queueDatePrompt.addEventListener('change', this.setQueueDate.bind(this)); } /* socket.io listeners */ handleScheduleLock(){ //Get queue lock button icon const icon = this.queueLockButton.querySelector('i'); if(this.client.queueLock){ this.queueLockButton.classList.remove('positive-button', 'bi-unlock-fill'); this.queueLockButton.classList.add('danger-button', 'bi-lock-fill'); }else{ this.queueLockButton.classList.remove('danger-button', 'bi-lock-fill'); this.queueLockButton.classList.add('positive-button', 'bi-unlock-fill'); } } /* queue control button functions */ toggleAddMedia(event){ //If the div is hidden if(this.addMediaDiv.style.display == 'none'){ //Light up the button this.addMediaButton.classList.add('positive-button'); //Show the div this.addMediaDiv.style.display = ''; }else{ //Unlight the button this.addMediaButton.classList.remove('positive-button'); //Hide the div this.addMediaDiv.style.display = 'none'; } } lockScroll(event, jumpToDay = true){ //If we're supposed to jump to the current day if(jumpToDay){ //Set schedule to current day this.setDay(); } //Enable scroll lock this.autoscroll = true; //If we have a time marker if(this.timeMarker != null){ //Light the indicator this.scrollLockButton.classList.add('positive-button'); //Render the marker early to insta-jump this.renderTimeMarker(); }else{ //Unlight the indicator this.scrollLockButton.classList.remove('positive-button'); } } toggleDateControl(event){ //If the div is hidden if(this.queueDateDiv.style.display == 'none'){ //Light up the button this.queueDateButton.classList.add('positive-button'); //Set date text this.queueDatePrompt.valueAsDate = utils.ux.localizeDate(this.day); //Show the div this.queueDateDiv.style.display = ''; }else{ //Unlight the button this.queueDateButton.classList.remove('positive-button'); //Hide the div this.queueDateDiv.style.display = 'none'; } } clearMedia(event){ //Call up the popup new clearPopup(event, this.client, null, this.ownerDoc); } lockSchedule(event){ client.socket.emit('lock'); } /* add queue controls */ queueLast(event){ //Send off the request this.client.socket.emit("queue",{url:this.addMediaLinkPrompt.value, title:this.addMediaNamePrompt.value}); //Clear out prompts this.addMediaLinkPrompt.value = ''; this.addMediaNamePrompt.value = ''; } queueAt(event){ //Call up the popup new schedulePopup(event, this.client, this.addMediaLinkPrompt.value, this.addMediaNamePrompt.value, null, this.ownerDoc); //Clear out prompts this.addMediaLinkPrompt.value = ''; this.addMediaNamePrompt.value = ''; } /* set date controls */ incrementDate(event){ //Increment day this.day.setDate(this.day.getDate() + 1); //Set day this.setDay(this.day); } decrementDate(event){ //Decrement day this.day.setDate(this.day.getDate() - 1); //Set day this.setDay(this.day); } setQueueDate(event){ //If we have a valid date if(this.queueDatePrompt.valueAsDate != null){ //Set the day this.setDay(utils.ux.normalizeDate(this.queueDatePrompt.valueAsDate)); } } setDay(date = new Date()){ //Set day this.day = date; //Zero out to midnight this.day.setHours(0,0,0,0); //Set prompt to current day this.queueDatePrompt.valueAsDate = utils.ux.localizeDate(this.day); //Re-render the queue this.renderQueue(); //Re-render/hide the time marker this.renderTimeMarker(); //If autoscroll is enabled if(this.autoscroll){ //Simulate a button click to un/re-light the button and trigger a scroll when the date is set to today this.lockScroll(null, false); } } 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); //Otherwise, if we're just scrolling }else{ //If we're looking at today if(utils.isSameDate(new Date(), this.day)){ //Unlock auto scroll this.unlockScroll(); } } } unlockScroll(){ //Disable scroll lock this.autoscroll = false; //Unlight the indicator this.scrollLockButton.classList.remove('positive-button'); } 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(', '); } resizeRender(event){ const date = new Date(); this.renderQueue(date); this.renderTimeMarker(date); } clearQueue(){ //Clear out queue container this.queueContainer.innerHTML = '';; //Clear out queue marker container this.queueMarkerContainer.innerHTML = ''; //Grab all related tooltips const tooltips = this.ownerDoc.body.querySelectorAll('.media-tooltip'); //clear them out since we're clearing out the elements with the event to remove them //These should clear out on their own on mousemove but this makes things look a *little* prettier :) for(let tooltip of tooltips){ tooltip.parentNode.remove(); } //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.ux.awaitNextFrame(); } //render the time marker this.renderTimeMarker(date, true); //render out the queue this.renderQueue(date); } renderQueue(date = new Date()){ //Clear out queue container this.queueContainer.innerHTML = ''; //for every entry in the queue for(let entry of this.client.queue){ //Check if item starts today const startsToday = utils.isSameDate(this.day, new Date(entry[1].startTime)); //Check if item ends today const endsToday = utils.isSameDate(this.day, new Date(entry[1].startTime + (entry[1].duration * 1000))); //If the item either starts or ends today var playsToday = (startsToday || endsToday); //If the media neither starts nor ends today if(!playsToday){ //set playsToday based on whether or not we're playing something fucking huge and it's covering all of today playsToday = utils.dateWithinRange(new Date(entry[1].startTime), new Date(entry[1].startTime + (entry[1].duration * 1000)), this.day); } //If part of the current item plays today if(playsToday){ //Create entry div const entryDiv = document.createElement('div'); entryDiv.classList.add('queue-entry'); //For each property of the media object for(let key of Object.keys(entry[1])){ //add it to its given dataset entryDiv.dataset[key] = entry[1][key]; } //If this item starts today if(startsToday){ //Place the top of the entry div based on time entryDiv.style.top = `${this.offsetByDate(new Date(entry[1].startTime))}px`; }else{ //Get dawn const dawn = new Date(); dawn.setHours(0,0,0,0); //Place item beginning at dawn entryDiv.style.top = `${this.offsetByDate(dawn)}px`; //Run apply the rest of the styling rules entryDiv.classList.add('started-before-today'); } //If the item ends today if(endsToday){ //Place the bottom of the entry div based on time entryDiv.style.bottom = `${this.offsetByDate(new Date(entry[1].startTime + (entry[1].duration * 1000)), true)}px`; }else{ //Get midnight const dusk = new Date(); dusk.setHours(23,59,59,999); //Place item beginning at dawn entryDiv.style.bottom = `${this.offsetByDate(dusk, true)}px`; //Run apply the rest of the styling rules entryDiv.classList.add('ends-after-today'); } //Create entry title const entryTitle = document.createElement('p'); entryTitle.textContent = entry[1].title; //Tooltip generation //tooltip div const tooltipDiv = document.createElement('div'); tooltipDiv.classList.add('media-tooltip'); //tooltip components //For each string for(let string of [ `Title: ${entry[1].title}`, `File Name: ${entry[1].fileName}`, `Source: ${entry[1].type}`, `Duration: ${entry[1].duration}`, `Start Time: ${new Date(entry[1].startTime).toLocaleString()}`, `End Time: ${new Date(entry[1].startTime + (entry[1].duration * 1000)).toLocaleString()}` ]){ //Create a 'p' node const component = document.createElement('p'); //Fill it with the string component.textContent = string; //Append it to the tooltip div tooltipDiv.append(component); } //Setup media tooltip entryDiv.addEventListener('mouseenter',(event)=>{ //If we're not dragging if(event.target.dataset['drag'] != 'true'){ //Display tooltip utils.ux.displayTooltip(event, tooltipDiv, false, null, true, this.ownerDoc); } }); //context menu const menuMap = new Map([ ["Play now", ()=>{this.client.socket.emit('move', {uuid: entry[1].uuid})}], ["Move To...", (event)=>{new reschedulePopup(event, this.client, entry[1], null, this.ownerDoc)}], ["Delete", ()=>{this.client.socket.emit('delete', {uuid: entry[1].uuid})}], ["Open in New Tab", ()=>{window.open(entry[1].url, '_blank').focus();}], ["Copy URL", ()=>{navigator.clipboard.writeText(entry[1].url);}], ]); //Setup drag n drop entryDiv.addEventListener('mousedown', clickEntry.bind(this)); //Setup context menu entryDiv.addEventListener('contextmenu', (event)=>{ //If we're not dragging if(event.target.dataset['drag'] != 'true'){ //Display context menu utils.ux.displayContextMenu(event, '', menuMap, this.ownerDoc); } }); //Append entry elements entryDiv.append(entryTitle); //Append entry this.queueContainer.append(entryDiv); } } function clickEntry(event){ //If it's not a left click if(event.buttons != 1){ //fuck off return; } //Create variables to hold width and height const height = event.target.offsetHeight; //If we havent set height or width if(event.target.style.height == ""){ //Preserve calculated entry height event.target.style.height = `${height}px`; } //Add set dragging CSS class to target event.target.classList.add('dragging-queue-entry'); //enable drag on target dataset event.target.dataset['drag'] = true; //Create a tooltip to show the time we're dragging to const timetip = new canopyUXUtils.tooltip('', false, null, this.ownerDoc); timetip.tooltip.classList.add('media-tooltip'); //Drag entry with mouse this.ownerDoc.body.addEventListener('mousemove', (nestedEvent)=>{(dragEntry.bind(this))(nestedEvent, event.target, timetip)}); //Drop on mouse up this.ownerDoc.body.addEventListener('mouseup', (nestedEvent)=>{(dropEntry.bind(this))(nestedEvent, event.target, timetip)}); //Disable selection on body this.ownerDoc.body.style.userSelect = 'none'; //Save top of target relative to window minus the mouse position as our drag offset event.target.dataset['dragoffset'] = (event.target.offsetTop + this.ownerDoc.defaultView.scrollY) - event.clientY; //Call the drag entry function to move the entry on click without re-writing the wheel (dragEntry.bind(this))(event, event.target, timetip); //Start dragscroll loop this.dragScrollTimer = setInterval(()=>{(dragScroll.bind(this))(event.target)}, 10); } function dragScroll(target){ //Stop timeout loop if(!target.isConnected || target.dataset['drag'] != "true"){ //Clear the interval clearInterval(this.dragScrollTimer); //Fuck off and die! return; } //Set minimum distance to detect const detectionDistance = 70; //Set value to devide distance from edge during scroll speed calculation const speedDevider = 6; //Get top boundaries distance from the top relative to the scroll top and set as top input let topInput = target.offsetTop - this.queueLayoutController.scrollTop; //Get bottom boundaries distance from the top relative to the scroll top and set as bottom input let bottomInput = this.queueContainer.offsetHeight - ((target.offsetTop + target.offsetHeight) + (this.queueLayoutController.scrollTopMax - this.queueLayoutController.scrollTop)); //If the item we're dragging is fackin uge' if(target.offsetHeight > (this.queueLayoutController.offsetHeight - ((detectionDistance * 2) + 20))){ //AND THEY FUCKING SAID YOU COULDN'T GET MOUSE POS OUTSIDE OF AN EVENT WITHOUT :HOVER TRICKS EAT MY FUCKING ASS topInput = Math.round(target.offsetTop - Number(target.dataset['dragoffset']) - (this.queueLayoutController.getBoundingClientRect().top + this.queueControlOffset.offsetHeight)); bottomInput = this.queueLayoutController.offsetHeight - (topInput + this.queueControlOffset.offsetHeight); } //If the top of the entry is within five pixels of the top of the parent and we have room to scroll up if(topInput < detectionDistance && this.queueLayoutController.scrollTop > 0){ //Unlock auto scroll this.unlockScroll(); //Filter out less than 0 from relative entry top to calculate speed const speed = Math.round(((detectionDistance) - (topInput < 0 ? 0 : topInput)) / speedDevider); //Scroll queue by distance to top this.queueLayoutController.scrollBy(0, -speed); //Add scroll amount to drag offset to keep entry aligned with mouse target.dataset['dragoffset'] = Number(target.dataset['dragoffset']) - speed //Move entry by speed to match new drag offset target.style.top = `${target.offsetTop - speed}px` //Otherwise if the bottom of the entry is within five pixels the bottom of the parent and we have room to scroll down }else if(bottomInput < (detectionDistance) && this.queueLayoutController.scrollTop < this.queueLayoutController.scrollTopMax){ //Unlock auto scroll this.unlockScroll(); //Calculate speed by distance to bottom const offsetBottom = bottomInput; //Filter out less than 0, reverse the range, and apply scroll dampen to avoid scrolling off the edge const speed = Math.round((detectionDistance - (offsetBottom < 0 ? 0 : offsetBottom)) / speedDevider) //Scroll queue by calculated speed this.queueLayoutController.scrollBy(0, speed); //Subtract speed from drag offset to keep aligned with mouse target.dataset['dragoffset'] = Number(target.dataset['dragoffset']) + speed; //Move entry by speed to match new drag offset target.style.top = `${target.offsetTop + speed}px` } } function dragEntry(event, target, timetip){ //Gross but works :P if(!target.isConnected || target.dataset['drag'] != "true"){ return; } //Get current start time const start = this.dateByOffset(target.offsetTop); //Position timetip timetip.moveToMouse(event); //Inject timetip label timetip.tooltip.innerHTML = [ `Start Time: ${utils.ux.timeStringFromDate(start, true)}`, `End Time: ${utils.ux.timeStringFromDate(new Date(start.getTime() + (target.dataset['duration'] * 1000)), true)}` ].join('
'); //Calculate offset from rest of window const windowOffset = this.queueContainer.offsetTop + this.ownerDoc.defaultView.scrollY; //Move the entry to the mouse offset by the target nodes height and the queue layouts scroll const entryTop = event.clientY + Number(target.dataset['dragoffset']) - windowOffset; //Set target vertical position target.style.top = `${entryTop}px`; } function dropEntry(event, target, timetip){ //Gross but works :P if(!target.isConnected){ return; } //Asynchronously send move command item by calculated time to server this.client.socket.emit('move', {uuid: target.dataset['uuid'], start: this.dateByOffset(target.offsetTop).getTime()}); //allow selection on body this.ownerDoc.body.style.userSelect = 'none'; //Remove timetip timetip.remove(); //Finish dragging target.dataset['drag'] = false; } } renderTimeMarker(date = new Date(), forceScroll = false){ //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're not looking at today if(!utils.isSameDate(this.day, new Date())){ //If we still have at time marker if(this.timeMarker != null){ this.timeMarker.remove(); this.timeMarker = null } //Stop here return; } //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 && (this.autoscroll || forceScroll)){ //Get height difference between window and queue layout controller const docDifference = this.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()){ //Clear out queue marker container this.queueMarkerContainer.innerHTML = ''; //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(); //Create array to hold entries for post processing const entries = []; //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 markerDiv = document.createElement('div'); markerDiv.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){ //Create marker label const markerLabel = document.createElement('p'); //If scale is over a minute then we don't need to display seconds markerLabel.textContent = utils.ux.timeStringFromDate(date, this.scale < 60) //Add marker label to marker span markerDiv.appendChild(markerLabel); } //Append marker to marker span markerDiv.appendChild(marker); //Append marker span to queue container this.queueMarkerContainer.appendChild(markerDiv); //Add it to our postprocessing list entries.push(markerDiv); } //If we made anything (should always be true :P) if(entries.length > 0){ //Set the margin for the first queue marker entries[0].classList.add('queue-marker-first'); //Set the margin for the last queue marker entries[entries.length - 1].classList.add('queue-marker-last'); } } offsetByDate(date = new Date(), bottomOffset = false){ //Pull start of day epoch from given date, make sure to use a new date object so we don't fuck up any date objects passed to us const dayEpoch = new Date(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]; } dateByOffset(input = 0){ //Get markers const markers = this.panelDocument.querySelectorAll('span.queue-marker'); //get range of position from top to bottom marker const range = [markers[0].offsetTop, markers[markers.length - 1].offsetTop] //get max offset relative to markers const offsetMax = range[1] - range[0]; //input offset + relative difference between top marker and parent devided by offset max //save as 'float' between 0 and 1 const relativeInput = ((input - range[0]) / offsetMax); //Get the currently viewed day const date = new Date(this.day); //Convert our 'float' from 0-1 to a time between 0-24 date.setHours(0,0,0,relativeInput * 86400000); //return our date return date; } } class schedulePopup{ constructor(event, client, url, title, cb, doc){ //Set Client this.client = client; //Set link this.url = url; //Set title this.title = title; //Set callback this.cb = cb; //Create media popup and call async constructor when done //unfortunately we cant call constructors asyncronously, and we cant call back to this from super, so we can't extend this as it stands :( this.popup = new canopyUXUtils.popup('/scheduleMedia', true, this.asyncConstructor.bind(this), doc); } asyncConstructor(){ //Grab required UI elements this.scheduleButton = this.popup.contentDiv.querySelector('#schedule-media-popup-schedule-button'); this.datePrompt = this.popup.contentDiv.querySelector('#schedule-media-popup-time-prompt'); //getCurrentDate const curDate = new Date(); //Zero out time to the nearest minute curDate.setSeconds(0,0); //Set the date prompt to the next minute, adjusted to display local time this.datePrompt.valueAsDate = utils.ux.localizeDate(curDate); //Setup input this.setupInput(); //If we have a function if(typeof cb == "function"){ //Call any callbacks we where given this.cb(); } } setupInput(){ //Setup input this.scheduleButton.addEventListener('click', this.schedule.bind(this)); this.popup.popupDiv.addEventListener('keydown', this.schedule.bind(this)); } schedule(event){ //If we clicked or hit enter if(event.key == null || event.key == "Enter"){ //Get localized input date const inputDate = utils.ux.normalizeDate(this.datePrompt.valueAsDate); //Tell the server to move the media this.client.socket.emit("queue",{url: this.url, title: this.title, start: inputDate.getTime()}); //Close the popup this.popup.closePopup(); } } } class reschedulePopup extends schedulePopup{ constructor(event, client, media, cb, doc){ //Call derived constructor super(event, client, null, null, cb, doc); //Set media this.media = media; } schedule(event){ //If we clicked or hit enter if(event.key == null || event.key == "Enter"){ //Get localized input date const inputDate = utils.ux.normalizeDate(this.datePrompt.valueAsDate); //Tell the server to move the media this.client.socket.emit('move', {uuid: this.media.uuid, start: inputDate.getTime()}); //Close the popup this.popup.closePopup(); } } } class clearPopup{ constructor(event, client, cb, doc){ //Set Client this.client = client; //Set callback this.cb = cb; console.log(doc); //Create media popup and call async constructor when done //unfortunately we cant call constructors asyncronously, and we cant call back to this from super, so we can't extend this as it stands :( this.popup = new canopyUXUtils.popup('/clearMedia', true, this.asyncConstructor.bind(this), doc); } asyncConstructor(){ //Grab required UI elements this.clearButton = this.popup.contentDiv.querySelector('#clear-media-popup-clear-button'); this.startDatePrompt = this.popup.contentDiv.querySelector('#clear-media-popup-start-time-prompt'); this.endDatePrompt = this.popup.contentDiv.querySelector('#clear-media-popup-end-time-prompt'); //getCurrentDate const curDate = new Date(); //Zero out current time to the nearest minute curDate.setSeconds(0,0); //Set the start date prompt to the next minute, adjusted to display local time this.startDatePrompt.valueAsDate = utils.ux.localizeDate(curDate); //Add 30 minutes curDate.setMinutes(curDate.getMinutes() + 30); //Set the end date prompt to 30 minutes in the futre, adjusted to display local time this.endDatePrompt.valueAsDate = utils.ux.localizeDate(curDate); //Setup input this.setupInput(); //If we have a function if(typeof cb == "function"){ //Call any callbacks we where given this.cb(); } } setupInput(){ //Setup input this.clearButton.addEventListener('click', this.clear.bind(this)); this.popup.popupDiv.addEventListener('keydown', this.clear.bind(this)); } clear(event){ //If we clicked or hit enter if(event.key == null || event.key == "Enter"){ //Get localized input date const inputStartDate = utils.ux.normalizeDate(this.startDatePrompt.valueAsDate); const inputEndDate = utils.ux.normalizeDate(this.endDatePrompt.valueAsDate); //Tell the server to clear media between the input range this.client.socket.emit("clear",{start: inputStartDate.getTime(), end: inputEndDate.getTime()}); //Close the popup this.popup.closePopup(); } } }