Continued work on media scheduler

This commit is contained in:
rainbow napkin 2025-01-28 09:43:39 -05:00
parent 9d01b4c962
commit d5a2a51be2
14 changed files with 415 additions and 54 deletions

View file

@ -57,7 +57,7 @@ class channel{
this.socket.on("error", console.log);
this.socket.on("queue", (data) => {
this.queue = data.queue;
this.queue = new Map(data.queue);
})
}
@ -74,7 +74,7 @@ class channel{
this.chatBox.handleClientInfo(data);
//Store queue for use by the queue panel
this.queue = data.queue;
this.queue = new Map(data.queue);
}
}

View file

@ -59,7 +59,7 @@ class chatBox{
this.autocompleteDisplay.addEventListener("click", this.tabComplete.bind(this));
this.sendButton.addEventListener("click", this.send.bind(this));
this.settingsIcon.addEventListener("click", ()=>{this.client.cPanel.setActivePanel(new panelObj(client))});
this.adminIcon.addEventListener("click", ()=>{this.client.cPanel.setActivePanel(new panelObj(client))});
this.adminIcon.addEventListener("click", ()=>{this.client.cPanel.setActivePanel(new queuePanel(client))});
this.emoteIcon.addEventListener("click", ()=>{this.client.cPanel.setActivePanel(new emotePanel(client))});
//Header icons
@ -310,11 +310,11 @@ class chatBox{
const limit = window.innerWidth * .2;
//Set width to target or 20vh depending on whether or not we've hit the width limit
this.chatPanel.style.width = targetChatWidth > limit ? `${targetChatWidth}px` : '20vh';
this.chatPanel.style.flexBasis = targetChatWidth > limit ? `${targetChatWidth}px` : '20vh';
//Fix busted layout
var pageBreak = document.body.scrollWidth - document.body.getBoundingClientRect().width;
this.chatPanel.style.width = `${this.chatPanel.getBoundingClientRect().width + pageBreak}px`;
this.chatPanel.style.flexBasis = `${this.chatPanel.getBoundingClientRect().width + pageBreak}px`;
//This sometimes gets called before userList ahs been initiated :p
if(this.client.userList != null){
this.client.userList.clickDragger.fixCutoff();

View file

@ -25,7 +25,7 @@ class cPanel{
this.poppedPanels = [];
//ClickDragger Objects
this.activePanelDragger = new canopyUXUtils.clickDragger("#cpanel-active-drag-handle", "#cpanel-active-div", false);
this.activePanelDragger = new canopyUXUtils.clickDragger("#cpanel-active-drag-handle", "#cpanel-active-div", false, null, false);
this.pinnedPanelDragger = new canopyUXUtils.clickDragger("#cpanel-pinned-drag-handle", "#cpanel-pinned-div", false, this.client.chatBox.clickDragger);
//Element Nodes
@ -150,6 +150,7 @@ class panelObj{
this.name = name;
this.pageURL = pageURL;
this.panelDocument = panelDocument;
this.ownerDoc = this.panelDocument.ownerDocument == null ? this.panelDocument : this.panelDocument.ownerDocument;
this.client = client;
}
@ -162,6 +163,8 @@ class panelObj{
}
docSwitch(){
//Set owner doc
this.ownerDoc = this.panelDocument.ownerDocument == null ? this.panelDocument : this.panelDocument.ownerDocument;
}
closer(){

View file

@ -8,11 +8,16 @@ class queuePanel extends panelObj{
//Create variable to hold rescale timer
this.rescaleTimer = null;
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
@ -24,6 +29,21 @@ class queuePanel extends panelObj{
//Re-acquire time marker
this.timeMarker = this.panelDocument.querySelector('#time-marker');
//Get main control buttons
this.addMediaButton = this.panelDocument.querySelector('#queue-add-media');
this.scrollLockButton = this.panelDocument.querySelector('#queue-scroll-lock');
//Get control divs
//Add Media
this.addMediaDiv = this.panelDocument.querySelector('#queue-media-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');
//Render out the queue
this.fullRender();
@ -38,13 +58,48 @@ class queuePanel extends panelObj{
defineListeners(){
this.client.socket.on("queue", (data) => {
this.renderQueue();
})
//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();})
}
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));
//control bar divs
this.queueLastButton.addEventListener('click', this.queueLast.bind(this))
}
toggleAddMedia(event){
if(this.addMediaDiv.style.display == 'none'){
this.addMediaDiv.style.display = '';
}else{
this.addMediaDiv.style.display = 'none';
}
}
queueLast(event){
//Send off the request
this.client.socket.emit("queue",{url:this.addMediaLinkPrompt.value, title:this.addMediaNamePrompt.value});
this.addMediaLinkPrompt.value = '';
this.addMediaNamePrompt.value = '';
}
lockScroll(event){
//Enable scroll lock
this.autoscroll = true;
//Light the indicator
this.scrollLockButton.classList.add('positive-button');
//Render the marker early to insta-jump
this.renderTimeMarker();
}
scaleScroll(event){
@ -85,7 +140,6 @@ class queuePanel extends panelObj{
//Clear out the queue UI
this.clearQueue();
//Calculate new scale
const newScale = this.scale + (scaleFactor * scrollDirection);
@ -107,6 +161,12 @@ class queuePanel extends panelObj{
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{
//Disable scroll lock
this.autoscroll = false;
//Unlight the indicator
this.scrollLockButton.classList.remove('positive-button');
}
}
@ -167,12 +227,26 @@ class queuePanel extends panelObj{
return timeStrings.join(', ');
}
resizeRender(event){
const date = new Date();
this.renderQueue(date);
this.renderTimeMarker(date);
}
clearQueue(){
//Clear out queue container
this.queueContainer.innerHTML = '';
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);
@ -206,13 +280,16 @@ class queuePanel extends panelObj{
}
//render the time marker
this.renderTimeMarker(date);
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){
//Create entry div
@ -224,9 +301,64 @@ class queuePanel extends panelObj{
entryDiv.style.bottom = `${this.offsetByDate(new Date(entry[1].startTime + (entry[1].duration * 1000)), true)}px`;
//Create entry title
const entryTitle = document.createElement('a');
const entryTitle = document.createElement('p');
entryTitle.textContent = entry[1].title;
entryTitle.href = entry[1].url;
//Tooltip generation
//tooltip div
const tooltipDiv = document.createElement('div');
tooltipDiv.classList.add('media-tooltip');
//tooltip title
const tooltipTitle = document.createElement('p');
tooltipTitle.textContent = `Title: ${entry[1].title}`;
//tooltip filename
const tooltipFilename = document.createElement('p');
tooltipFilename.textContent = `File Name: ${entry[1].fileName}`;
//tooltip source
const tooltipSource = document.createElement('p');
tooltipSource.textContent = `Source: ${entry[1].type}`;
//tooltip duration
const tooltipDuration = document.createElement('p');
tooltipDuration.textContent = `Duration: ${entry[1].duration}`;
//tooltip start
const tooltipStart = document.createElement('p');
tooltipStart.textContent = `Start Time: ${new Date(entry[1].startTime).toLocaleString()}`;
//tooltip end
const tooltipEnd = document.createElement('p');
tooltipEnd.textContent = `Start Time: ${new Date(entry[1].startTime + (entry[1].duration * 1000)).toLocaleString()}`;
//append components
for(let component of [
tooltipTitle,
tooltipFilename,
tooltipSource,
tooltipDuration,
tooltipStart,
tooltipEnd
]){
tooltipDiv.append(component);
}
//Setup media tooltip
entryDiv.addEventListener('mouseenter',(event)=>{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...", ()=>{}],
["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 context menu
entryDiv.addEventListener('contextmenu', (event)=>{utils.ux.displayContextMenu(event, '', menuMap, this.ownerDoc);});
//Append entry elements
entryDiv.append(entryTitle);
@ -236,7 +368,7 @@ class queuePanel extends panelObj{
}
}
renderTimeMarker(date = new Date()){
renderTimeMarker(date = new Date(), forceScroll = false){
//Calculate marker position by date
const markerPosition = Math.round(this.offsetByDate(date));
@ -260,12 +392,9 @@ class queuePanel extends panelObj{
this.timeMarker.style.top = `${markerPosition}px`;
//If the panel document isn't null (we're not actively switching panels)
if(this.panelDocument != null){
//Grab the current owner document object
const ownerDoc = this.panelDocument.ownerDocument == null ? this.panelDocument : this.panelDocument.ownerDocument;
if(this.panelDocument != null && (this.autoscroll || forceScroll)){
//Get height difference between window and queue layout controller
const docDifference = ownerDoc.defaultView.innerHeight - this.queueLayoutController.getBoundingClientRect().height;
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
@ -292,6 +421,9 @@ class queuePanel extends panelObj{
}
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);
@ -350,8 +482,8 @@ class queuePanel extends panelObj{
}
offsetByDate(date = new Date(), bottomOffset = false){
//Pull start of day epoch from given date
const dayEpoch = structuredClone(date).setHours(0,0,0,0);
//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

View file

@ -229,7 +229,7 @@ class player{
this.showVideoIcon.style.display = "flex";
this.client.chatBox.hideChatIcon.style.display = "none";
//Need to clear the width from the split, or else it doesn't display properly
this.client.chatBox.chatPanel.style.width = "100%";
this.client.chatBox.chatPanel.style.flexBasis = "100%";
}
}

View file

@ -71,17 +71,21 @@ class canopyUXUtils{
}
}
displayTooltip(event, content, ajaxTooltip, cb, soft = false){
displayTooltip(event, content, ajaxTooltip, cb, soft = false, doc = document){
//Create the tooltip
const tooltip = new canopyUXUtils.tooltip(content, ajaxTooltip, ()=>{
//Call mouse move again after ajax load to re-calculate position within context of the new content
tooltip.moveToMouse(event);
//If this is an ajax tooltip
if(ajaxTooltip){
//Call mouse move again after ajax load to re-calculate position within context of the new content
tooltip.moveToMouse(event);
}
//If we have a callback function
if(typeof cb == "function"){
//Call async callback
cb();
}
});
}, doc);
//Move the tooltip with the mouse
event.target.addEventListener('mousemove', tooltip.moveToMouse.bind(tooltip));
@ -92,18 +96,29 @@ class canopyUXUtils{
//remove the tooltip on mouseleave
event.target.addEventListener('mouseleave', tooltip.remove.bind(tooltip));
//Kill tooltip with parent
doc.body.addEventListener('mousemove', killWithParent);
if(soft){
//remove the tooltip on context menu open
event.target.addEventListener('click', tooltip.remove.bind(tooltip));
event.target.addEventListener('contextmenu', tooltip.remove.bind(tooltip));
}
function killWithParent(){
//If the tooltip parent no longer exists
if(!event.target.isConnected){
//Kill the whole family :D
tooltip.remove();
}
}
}
displayContextMenu(event, title, menuMap){
displayContextMenu(event, title, menuMap, doc){
event.preventDefault();
//Create context menu
const contextMenu = new canopyUXUtils.contextMenu(title, menuMap);
const contextMenu = new canopyUXUtils.contextMenu(title, menuMap, doc);
//Move context menu to mouse
contextMenu.moveToMouse(event);
@ -167,12 +182,13 @@ class canopyUXUtils{
}
static tooltip = class{
constructor(content, ajaxTooltip = false, cb){
constructor(content, ajaxTooltip = false, cb, doc = document){
//Define non-tooltip node values
this.content = content;
this.ajaxPopup = ajaxTooltip;
this.cb = cb;
this.id = Math.random();
this.doc = doc;
//create and append tooltip
this.tooltip = document.createElement('div');
@ -190,7 +206,15 @@ class canopyUXUtils{
this.tooltip.textContent = "Loading tooltip..."
this.tooltip.innerHTML = await utils.ajax.getTooltip(this.content);
}else{
this.tooltip.innerHTML = this.content;
//If the content we received is a string
if(typeof this.content == "string"){
//Use it
this.tooltip.innerHTML = this.content;
//Otherwise
}else{
//Append it as a node
this.tooltip.appendChild(this.content);
}
}
if(this.cb){
@ -200,14 +224,14 @@ class canopyUXUtils{
}
displayTooltip(){
document.body.appendChild(this.tooltip);
this.doc.body.appendChild(this.tooltip);
}
moveToPos(x,y){
//If the distance between the left edge of the window - the window width is more than the width of our tooltip
if((window.innerWidth - (window.innerWidth - x)) > this.tooltip.getBoundingClientRect().width){
if((this.doc.defaultView.innerWidth - (this.doc.defaultView.innerWidth - x)) > this.tooltip.getBoundingClientRect().width){
//Push it to the right edge of the cursor, where the hard edge typically is
this.tooltip.style.right = `${window.innerWidth - x}px`;
this.tooltip.style.right = `${this.doc.defaultView.innerWidth - x}px`;
this.tooltip.style.left = '';
//otherwise, if we're close to the edge
}else{
@ -218,9 +242,9 @@ class canopyUXUtils{
//If the distance between the top edge of the window - the window height is more than the heigt of our tooltip
if((window.innerHeight - (window.innerHeight - y)) > this.tooltip.getBoundingClientRect().height){
if((this.doc.defaultView.innerHeight - (this.doc.defaultView.innerHeight - y)) > this.tooltip.getBoundingClientRect().height){
//Push it above the mouse
this.tooltip.style.bottom = `${window.innerHeight - y}px`;
this.tooltip.style.bottom = `${this.doc.defaultView.innerHeight - y}px`;
this.tooltip.style.top = '';
//otherwise if we're close to the edge
}else{
@ -243,9 +267,9 @@ class canopyUXUtils{
}
static contextMenu = class extends this.tooltip{
constructor(title, menuMap){
constructor(title, menuMap, doc = document){
//Call inherited tooltip constructor
super('Loading Menu...');
super('Loading Menu...', false, null, doc);
//Set tooltip class
this.tooltip.classList.add('context-menu');
@ -281,8 +305,8 @@ class canopyUXUtils{
//Create event listener to remove tooltip whenever anything is clicked, inside or out of the menu
//Little hacky but we have to do it a bit later to prevent the opening event from closing the menu
setTimeout(()=>{document.body.addEventListener('click', this.remove.bind(this));}, 1);
setTimeout(()=>{document.body.addEventListener('contextmenu', this.remove.bind(this));}, 1);
setTimeout(()=>{this.doc.body.addEventListener('click', this.remove.bind(this));}, 1);
setTimeout(()=>{this.doc.body.addEventListener('contextmenu', this.remove.bind(this));}, 1);
}
}
@ -390,7 +414,7 @@ class canopyUXUtils{
}
static clickDragger = class{
constructor(handle, element, leftHandle = true, parent){
constructor(handle, element, leftHandle = true, parent, flex = true){
//Pull needed nodes
this.handle = document.querySelector(handle);
this.element = document.querySelector(element);
@ -407,6 +431,9 @@ class canopyUXUtils{
//we put a click dragger in yo click dragger so you could click and drag while you click and drag
this.parent = parent;
//Whether or not click dragger is in a flexbox
this.flex = flex;
//Setup our event listeners
this.setupInput();
}
@ -453,7 +480,13 @@ class canopyUXUtils{
//if we're not breaking the page, or we're moving left
if((!this.breakingScreen && pageBreak <= 0) || event.clientX < this.handle.getBoundingClientRect().left){
//Apply difference to width
if(this.flex){
this.element.style.flexBasis = `${this.calcWidth(difference)}vw`;
}
//I know it's kludgy to apply this along-side flex basis but it fixes some nasty bugs with nested draggers
//Don't @ me, it's not like i'm an actual web developer anyways :P
this.element.style.width = `${this.calcWidth(difference)}vw`;
//If we let go here, the width isn't breaking anything so there's nothing to fix.
this.breakingScreen = false;
}else{
@ -477,6 +510,9 @@ class canopyUXUtils{
fixCutoff(standalone = true, pageBreak = document.body.scrollWidth - document.body.getBoundingClientRect().width){
//Fix the page width
if(this.flex){
this.element.style.flexBasis = `${this.calcWidth(this.element.getBoundingClientRect().width + pageBreak)}vw`;
}
this.element.style.width = `${this.calcWidth(this.element.getBoundingClientRect().width + pageBreak)}vw`;
//If we're calling this outside of drag() (regardless of draglock unless set otherwise)