1304 lines
50 KiB
JavaScript
1304 lines
50 KiB
JavaScript
/*Canopy - The next generation of stoner streaming software
|
|
Copyright (C) 2024-2025 Rainbownapkin and the TTN Community
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU Affero General Public License as
|
|
published by the Free Software Foundation, either version 3 of the
|
|
License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Affero General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
|
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;
|
|
|
|
//Setup child classes
|
|
this.playlistManager = new playlistManager(client, panelDocument, this);
|
|
|
|
//Define non-input event listeners
|
|
this.defineListeners();
|
|
}
|
|
|
|
docSwitch(){
|
|
//Clear timetips
|
|
this.killTimetips();
|
|
|
|
//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.playlistMenuButton = this.panelDocument.querySelector("#queue-playlists");
|
|
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');
|
|
this.playlistDiv = this.panelDocument.querySelector('#queue-playlist-prompts');
|
|
|
|
//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();
|
|
|
|
//Pass the new panelDoc and docSwitch() call down to child classes
|
|
this.playlistManager.panelDocument = this.panelDocument;
|
|
this.playlistManager.docSwitch();
|
|
}
|
|
|
|
closer(){
|
|
//Clear any remaining timers
|
|
clearTimeout(this.renderIntervalTimer);
|
|
//Clear timetips
|
|
this.killTimetips();
|
|
}
|
|
|
|
|
|
defineListeners(){
|
|
//Render queue when we receive a new copy of the queue data from the server
|
|
this.client.socket.on("clientMetadata", () => {this.renderQueue();});
|
|
this.client.socket.on("queue", () => {this.renderQueue();});
|
|
this.client.socket.on("start", () => {this.renderQueue();});
|
|
this.client.socket.on("end", () => {this.renderQueue();});
|
|
this.client.socket.on("lock", this.handleScheduleLock.bind(this));
|
|
this.client.socket.on("error", this.handleQueueError.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.playlistMenuButton.addEventListener('click', this.togglePlaylistDiv.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');
|
|
}
|
|
}
|
|
|
|
handleQueueError(data){
|
|
//Create a flag to reload the queue
|
|
let reloadQueue = false;
|
|
|
|
//Check errors
|
|
for(let error of data.errors){
|
|
//If we have a queue error
|
|
if(error.type == "queue"){
|
|
//Throw the reload flag
|
|
reloadQueue = true;
|
|
}
|
|
}
|
|
|
|
//If we need to reload the queue
|
|
if(reloadQueue){
|
|
//Do so
|
|
this.renderQueue();
|
|
}
|
|
}
|
|
|
|
/* 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';
|
|
}
|
|
}
|
|
|
|
togglePlaylistDiv(event){
|
|
//If the div is hidden
|
|
if(this.playlistDiv.style.display == 'none'){
|
|
//Query playlists
|
|
client.socket.emit('getChannelPlaylists');
|
|
|
|
//Light up the button
|
|
this.playlistMenuButton.classList.add('positive-button');
|
|
//Show the div
|
|
this.playlistDiv.style.display = '';
|
|
}else{
|
|
//Unlight the button
|
|
this.playlistMenuButton.classList.remove('positive-button');
|
|
//Hide the div
|
|
this.playlistDiv.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){
|
|
//I could sit here and work out why timetips break everything on scalescroll
|
|
//Then again you wouldn't want phantom timetips surviving the re-scale so you might as well get two birds stoned at once :P
|
|
this.killTimetips();
|
|
|
|
//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:<br>${utils.ux.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');
|
|
}
|
|
|
|
resizeRender(event){
|
|
const date = new Date();
|
|
this.renderQueue(date);
|
|
this.renderTimeMarker(date);
|
|
}
|
|
|
|
clearQueue(){
|
|
//If we have no body
|
|
if(this.ownerDoc.body == null){
|
|
//We have bigger issues
|
|
return;
|
|
}
|
|
|
|
//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.renderIntervalTimer);
|
|
|
|
//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);
|
|
|
|
//Grab the first marker
|
|
let firstMarker = this.panelDocument.querySelector('.queue-marker-first');
|
|
|
|
//Loop until first marker is properly positioned
|
|
while(firstMarker.offsetTop > 0){
|
|
//wait a few frames so the scale can finish rendering, because dom function aren't async for some fucking reason
|
|
await utils.ux.awaitNextFrame();
|
|
}
|
|
|
|
//render the time marker w/ force scroll
|
|
this.renderTimeMarker(date, true);
|
|
|
|
//Kick off render interval
|
|
this.renderInterval(date);
|
|
|
|
//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`;
|
|
|
|
//Apply style rules for items that starrted yesterday
|
|
entryDiv.classList.add('started-yesterday');
|
|
}
|
|
|
|
//If the item ends today
|
|
if(endsToday){
|
|
//Place the bottom of the entry div based on time
|
|
entryDiv.style.bottom = `${this.offsetByDate(new Date(this.getMediaEnd(entry[1])), 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-tomorrow');
|
|
}
|
|
|
|
//If we started in the middle of the video
|
|
if(entry[1].startTimeStamp > 0){
|
|
entryDiv.classList.add('started-late');
|
|
}
|
|
|
|
//If we ended early
|
|
if(entry[1].earlyEnd != null){
|
|
entryDiv.classList.add('ended-early');
|
|
}
|
|
|
|
//Create entry title
|
|
const entryTitle = document.createElement('p');
|
|
entryTitle.textContent = utils.unescapeEntities(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: ${utils.ux.humieFriendlyDuration(entry[1].duration)}`,
|
|
`Start Time: ${new Date(entry[1].startTime).toLocaleString()}${entry[1].startTimeStamp == 0 ? '' : ' (Started Late)'}`,
|
|
`End Time: ${new Date(this.getMediaEnd(entry[1])).toLocaleString()}${entry[1].earlyEnd == null ? '' : ' (Ended Early)'}`
|
|
]){
|
|
//Create a 'p' node
|
|
const component = document.createElement('p');
|
|
//Fill it with the string
|
|
component.textContent = utils.unescapeEntities(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);
|
|
}
|
|
});
|
|
|
|
//Create context menu map
|
|
const menuMap = new Map();
|
|
const now = new Date();
|
|
|
|
//If the item hasn't started yet
|
|
if(entry[1].startTime > now.getTime()){
|
|
//Add 'Play' option to context menu
|
|
menuMap.set("Play now", ()=>{this.client.socket.emit('move', {uuid: entry[1].uuid})});
|
|
//Add 'Move To...' option to context menu
|
|
menuMap.set("Move To...", (event)=>{new reschedulePopup(event, this.client, entry[1], null, this.ownerDoc)});
|
|
//Otherwise, if the item is currently playing
|
|
}else if(this.getMediaEnd(entry[1]) > now.getTime()){
|
|
//Add 'Stop' option to context menu
|
|
menuMap.set("Stop", ()=>{this.client.socket.emit('stop', {uuid: entry[1].uuid})});
|
|
//Add the Now Playing glow, not the prettiest place to add this, but why let a good conditional go to waste?
|
|
entryDiv.classList.add('now-playing');
|
|
//Otherwise, if the item has been archived
|
|
}else{
|
|
entryDiv.classList.add('archived');
|
|
}
|
|
|
|
//Add 'Delete' option to context menu
|
|
menuMap.set("Delete", ()=>{this.client.socket.emit('delete', {uuid: entry[1].uuid})})
|
|
//Add 'New Tab' option to context menu
|
|
menuMap.set("Open in New Tab", ()=>{window.open(entry[1].url, '_blank').focus();})
|
|
//Add 'Copy URL' option to context menu
|
|
menuMap.set("Copy URL", ()=>{navigator.clipboard.writeText(entry[1].url);})
|
|
|
|
//If the item hasn't yet ended
|
|
if(this.getMediaEnd(entry[1]) > now.getTime()){
|
|
//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);
|
|
}
|
|
|
|
//Render out any playing livestreams
|
|
this.renderLiveStream(date);
|
|
}
|
|
|
|
function clickEntry(event){
|
|
//If it's not a left click
|
|
if(event.buttons != 1){
|
|
//fuck off
|
|
return;
|
|
}
|
|
|
|
//Grab existing height
|
|
let height = event.target.offsetHeight;
|
|
let cutoffOffset = 0;
|
|
|
|
//If the item got cut-off at the bottom
|
|
if(event.target.classList.contains("ends-tomorrow") || event.target.classList.contains("ended-early")){
|
|
//Calculate height from duration
|
|
height = this.offsetByMilliseconds(Number(event.target.dataset['duration']) * 1000);
|
|
//If the item got cut-off at the top
|
|
}else if(event.target.classList.contains('started-yesterday') || event.target.classList.contains("started-late")){
|
|
//Keep old height for now
|
|
const oldHeight = height;
|
|
|
|
//Calculate height from duration
|
|
height = this.offsetByMilliseconds(Number(event.target.dataset['duration']) * 1000);
|
|
|
|
//Calculate the mouse offset needed to keep it properly placed relative to the original click point
|
|
cutoffOffset = height - oldHeight;
|
|
}
|
|
|
|
//Remove any cut-off borders
|
|
event.target.classList.remove('ends-tomorrow', 'started-yesterday', 'ended-early', 'started-late');
|
|
|
|
|
|
//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;
|
|
|
|
//Kill existing timetips
|
|
this.killTimetips();
|
|
|
|
//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','media-timetip');
|
|
timetip.tooltip.addEventListener('mousemove', this.killTimetips.bind(this));
|
|
|
|
|
|
//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)});
|
|
|
|
//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 - cutoffOffset;
|
|
|
|
//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
|
|
//Normally wouldn't do innerHTML but these values are calculated serverside and it saves us making a <br> element
|
|
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('<br>');
|
|
|
|
//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){
|
|
//Gross but works :P
|
|
if(!target.isConnected || target.dataset['drag'] != "true"){
|
|
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';
|
|
|
|
//kill timetips
|
|
this.killTimetips();
|
|
|
|
//Finish dragging
|
|
target.dataset['drag'] = false;
|
|
}
|
|
|
|
}
|
|
|
|
killTimetips(event){
|
|
//If we have an event and it's holding any mouse buttons
|
|
if(event != null && event.buttons != 0){
|
|
//Fuck off and die
|
|
return;
|
|
}
|
|
|
|
//Find any existing timetips
|
|
for(const timetip of this.ownerDoc.querySelectorAll('.media-timetip')){
|
|
//nukem
|
|
timetip.remove();
|
|
}
|
|
}
|
|
|
|
renderInterval(date = new Date()){
|
|
this.renderTimeMarker(date);
|
|
this.renderLiveStream(date);
|
|
|
|
|
|
//Clear update timer
|
|
clearTimeout(this.renderIntervalTimer);
|
|
|
|
//Set timer to update marker every second
|
|
this.renderIntervalTimer = setTimeout(this.renderInterval.bind(this), 1000);
|
|
}
|
|
|
|
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
|
|
return;
|
|
}
|
|
|
|
//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
|
|
});
|
|
}
|
|
}
|
|
|
|
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 <hr> 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');
|
|
}
|
|
}
|
|
|
|
renderLiveStream(date = new Date()){
|
|
//Grab all live queue entries
|
|
const staleEntry = this.queueContainer.querySelector('.queue-entry.live');
|
|
|
|
//If we're not livestreaming
|
|
if(client.player.mediaHandler.type != "livehls"){
|
|
//If we have a stale entry
|
|
if(staleEntry != null){
|
|
//Remove stale entry since we're no longer streaming
|
|
staleEntry.remove();
|
|
}
|
|
|
|
//Fuck off and die
|
|
return;
|
|
}
|
|
|
|
//Grab currently playing media
|
|
const nowPlaying = client.player.mediaHandler.nowPlaying;
|
|
|
|
//If we don't have a good stale entry to re-use
|
|
if(staleEntry == null || staleEntry.dataset.uuid != nowPlaying.uuid){
|
|
//If it wasn't null but just old
|
|
if(staleEntry != null){
|
|
//Nukem
|
|
staleEntry.remove();
|
|
}
|
|
|
|
//Create entry div
|
|
const entryDiv = document.createElement('div');
|
|
entryDiv.classList.add('queue-entry', 'live');
|
|
|
|
//Iterate through nowPlaying's properties
|
|
for(let key of Object.keys(nowPlaying)){
|
|
//Add them to the entry div's dataset
|
|
entryDiv.dataset[key] = nowPlaying[key];
|
|
}
|
|
|
|
//Convert start epoch to JS date object
|
|
const started = new Date(nowPlaying.startTime);
|
|
|
|
//If this started today
|
|
if(utils.isSameDate(this.day, started)){
|
|
//Set entryDiv top-border location based on start time
|
|
entryDiv.style.top = `${this.offsetByDate(started)}px`
|
|
}else{
|
|
//Get dawn
|
|
const dawn = new Date();
|
|
dawn.setHours(0,0,0,0)
|
|
|
|
//Place item at the beginning of dawn
|
|
entryDiv.style.top = `${this.offsetByDate(dawn)}px`;
|
|
|
|
//Apply rest of the styling rules for items that started yestarday
|
|
entryDiv.classList.add('started-yesterday')
|
|
}
|
|
|
|
//Create entry title
|
|
const entryTitle = document.createElement('p');
|
|
entryTitle.textContent = utils.unescapeEntities(nowPlaying.title);
|
|
|
|
//Set entry div bottom-border location based on current time
|
|
entryDiv.style.bottom = `${this.offsetByDate(date, true)}px`
|
|
|
|
//Assembly entryDiv
|
|
entryDiv.appendChild(entryTitle);
|
|
|
|
//Setup media tooltip
|
|
entryDiv.addEventListener('mouseenter',(event)=>{
|
|
//Construct tooltip on mouseover to re-calculate duration for live media
|
|
const tooltipDiv = buildTooltip();
|
|
|
|
//Display tooltip
|
|
utils.ux.displayTooltip(event, tooltipDiv, false, null, true, this.ownerDoc);
|
|
});
|
|
|
|
//Append entry div to queue container
|
|
this.queueContainer.appendChild(entryDiv);
|
|
}else{
|
|
//Update existing entry
|
|
staleEntry.style.bottom = `${this.offsetByDate(date, true)}px`
|
|
}
|
|
|
|
//Keep tooltip date seperate so it re-calculates live duration properly
|
|
function buildTooltip(date = new Date()){
|
|
//Tooltip generation
|
|
//tooltip div
|
|
const tooltipDiv = document.createElement('div');
|
|
tooltipDiv.classList.add('media-tooltip');
|
|
|
|
//tooltip components
|
|
const tooltipStrings = [
|
|
`Title: ${nowPlaying.title}`,
|
|
`File Name: ${nowPlaying.fileName}`,
|
|
`Source: HLS Livestream (${nowPlaying.url})`,
|
|
`Duration: ${utils.ux.humieFriendlyDuration((date.getTime() - nowPlaying.startTime) / 1000)}`,
|
|
`Start Time: ${new Date(nowPlaying.startTime).toLocaleString()}`,
|
|
];
|
|
|
|
//For each string in the tooltip
|
|
for(const string of tooltipStrings){
|
|
|
|
//Create a 'p' node
|
|
const component = document.createElement('p');
|
|
//Fill it with the string
|
|
component.textContent = utils.unescapeEntities(string);
|
|
|
|
//Append it to the tooltip div
|
|
tooltipDiv.append(component);
|
|
}
|
|
|
|
//Create End Date
|
|
const source = document.createElement('p');
|
|
const liveSpan = document.createElement('span');
|
|
|
|
//Fill end date label
|
|
source.textContent = "End Time: "
|
|
liveSpan.textContent = 'LIVE'
|
|
|
|
//Set class
|
|
liveSpan.classList.add('critical-danger-text');
|
|
|
|
//Assemble end date
|
|
source.appendChild(liveSpan);
|
|
tooltipDiv.appendChild(source);
|
|
|
|
return tooltipDiv;
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
//Calculate the offset from todays milliseconds
|
|
return this.offsetByMilliseconds(curTime, bottomOffset);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
offsetByMilliseconds(input = 0, bottomOffset = false){
|
|
//Convert amount of milliseconds to a float, 0 representing the start of the day and 1 representing the end.
|
|
//Make sure to reverse it if bottomOffset is set to true
|
|
const timeFloat = bottomOffset ? 1 - (input / 86400000) : input / 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];
|
|
}
|
|
|
|
getMediaEnd(media){
|
|
//If we have an early end
|
|
if(media.earlyEnd != null){
|
|
return media.startTime + (media.earlyEnd * 1000);
|
|
//Otherwise
|
|
}else{
|
|
return media.startTime + ((media.duration - media.startTimeStamp) * 1000);
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
//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();
|
|
}
|
|
}
|
|
} |