Added schedule clearing and scroll to drag to schedule panel.

This commit is contained in:
rainbow napkin 2025-02-07 06:01:20 -05:00
parent 56ab5a16ec
commit c04edb6691
8 changed files with 466 additions and 120 deletions

View file

@ -33,13 +33,18 @@ class queuePanel extends panelObj{
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');
//Get control divs
this.addMediaDiv = this.panelDocument.querySelector('#queue-media-prompts');
@ -86,10 +91,12 @@ class queuePanel extends panelObj{
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));
//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));
@ -147,14 +154,30 @@ class queuePanel extends panelObj{
}
}
clearMedia(event){
//Call up the popup
new clearPopup(event, this.client, null);
}
/* 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);
//Clear out prompts
this.addMediaLinkPrompt.value = '';
this.addMediaNamePrompt.value = '';
}
/* set date controls */
incrementDate(event){
//Increment day
@ -264,14 +287,19 @@ class queuePanel extends panelObj{
}else{
//If we're looking at today
if(utils.isSameDate(new Date(), this.day)){
//Disable scroll lock
this.autoscroll = false;
//Unlight the indicator
this.scrollLockButton.classList.remove('positive-button');
//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){
@ -413,7 +441,12 @@ class queuePanel extends panelObj{
//Create entry div
const entryDiv = document.createElement('div');
entryDiv.classList.add('queue-entry');
entryDiv.dataset['uuid'] = entry[1].uuid;
//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){
@ -528,41 +561,125 @@ class queuePanel extends panelObj{
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;
//Drag entry with mouse
this.ownerDoc.body.addEventListener('mousemove', (nestedEvent)=>{(dragEntry.bind(this))(nestedEvent, event.target)});
//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');
//Drop on moust up
this.ownerDoc.body.addEventListener('mouseup', (nestedEvent)=>{(dropEntry.bind(this))(nestedEvent, event.target)});
//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.getBoundingClientRect().top + this.ownerDoc.defaultView.scrollY) - event.clientY);
event.target.dataset['dragoffset'] = event.clientY - (event.target.getBoundingClientRect().top + this.ownerDoc.defaultView.scrollY);
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);
(dragEntry.bind(this))(event, event.target, timetip);
//Start dragscroll loop
this.dragScrollTimer = setInterval(()=>{(dragScroll.bind(this))(event.target)}, 10);
}
function dragEntry(event, target){
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) + 10))){
//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('<br>');
//Calculate offset from rest of window
const windowOffset = this.queueContainer.getBoundingClientRect().top + this.ownerDoc.defaultView.scrollY;
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
target.style.top = `${event.clientY - Number(target.dataset['dragoffset']) - windowOffset}px`;
const entryTop = event.clientY + Number(target.dataset['dragoffset']) - windowOffset;
//Set target vertical position
target.style.top = `${entryTop}px`;
}
function dropEntry(event, target){
function dropEntry(event, target, timetip){
//Gross but works :P
if(!target.isConnected){
return;
@ -574,74 +691,12 @@ class queuePanel extends panelObj{
//allow selection on body
this.ownerDoc.body.style.userSelect = 'none';
//Remove timetip
timetip.remove();
//Finish dragging
target.dataset['drag'] = false;
}
class reschedulePopup{
constructor(event, client, media, cb){
//Set Client
this.client = client;
//Set media
this.media = media;
//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));
}
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 current date to midnight
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);
//If someone is trying to schedule in the past
if(inputDate < new Date().getTime()){
//Schedule now
this.client.socket.emit('move', {uuid: this.media.uuid});
//Otherwise
}else{
//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();
}
}
}
}
renderTimeMarker(date = new Date(), forceScroll = false){
@ -742,23 +797,11 @@ class queuePanel extends panelObj{
//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
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`
}
//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);
@ -820,8 +863,8 @@ class queuePanel extends panelObj{
//save as 'float' between 0 and 1
const relativeInput = ((input - range[0]) / offsetMax);
//Get the current date
const date = new Date();
//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);
@ -829,4 +872,148 @@ class queuePanel extends panelObj{
//return our date
return date;
}
}
class schedulePopup{
constructor(event, client, url, title, cb){
//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));
}
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){
//Call derived constructor
super(event, client, null, null, cb);
//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){
//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));
}
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();
}
}
}

View file

@ -101,6 +101,9 @@ class player{
//Re-size to aspect since video may now be a different size
this.client.chatBox.resizeAspect();
//Sync off of starter time stamp
this.mediaHandler.sync(data.timestamp);
}
sync(data){