Continued work on media schedule panel.

This commit is contained in:
rainbow napkin 2025-01-25 08:37:27 -05:00
parent 42c20455e5
commit 07d1a37453
8 changed files with 344 additions and 67 deletions

View file

@ -14,7 +14,8 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. %> along with this program. If not, see <https://www.gnu.org/licenses/>. %>
<link rel="stylesheet" type="text/css" href="/css/panel/queue.css"> <link rel="stylesheet" type="text/css" href="/css/panel/queue.css">
<div id="queue-controls"> <div id="queue-panel-layout-controller">
<div id="queue-controls">
<span id="queue-media-prompts"> <span id="queue-media-prompts">
<div class="panel-control-prompt control-prompt"> <div class="panel-control-prompt control-prompt">
<input placeholder="Media Link..." id="media-link-input" class="control-prompt"> <input placeholder="Media Link..." id="media-link-input" class="control-prompt">
@ -25,6 +26,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
<button id="queue-last-button" class="positive-button">Queue Last</button> <button id="queue-last-button" class="positive-button">Queue Last</button>
</div> </div>
</span> </span>
</div> </div>
<div id="queue"> <div id="queue">
<div id="queue-marker-container"></div>
<div id="queue-container"></div>
</div>
</div> </div>

View file

@ -242,14 +242,12 @@ span.user-entry{
top: 0; top: 0;
bottom: 0; bottom: 0;
left: 0; left: 0;
overflow-y: auto;
scrollbar-width: thin; scrollbar-width: thin;
width: 75%; width: 75%;
} }
#cpanel-pinned-div{ #cpanel-pinned-div{
position: relative; position: relative;
overflow-y: auto;
scrollbar-width: thin; scrollbar-width: thin;
} }

View file

@ -16,6 +16,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
.cpanel-div{ .cpanel-div{
flex-direction: column; flex-direction: column;
overflow: hidden; overflow: hidden;
flex: 1;
} }
.cpanel-header-div{ .cpanel-header-div{
@ -38,3 +39,18 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
.cpanel-header-icon{ .cpanel-header-icon{
cursor: pointer; cursor: pointer;
} }
.cpanel-doc{
flex: 1;
overflow: auto;
}
.cpanel-body{
display: block;
height: 100%;
}
#cpanel-div{
display: flex;
height: 100%;
}

View file

@ -13,18 +13,36 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>.*/ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
#queue{ #queue-panel-layout-controller{
position: relative; display: flex;
display: grid; flex-direction: column;
grid-template-columns: auto 75%; overflow-y: auto;
height: 100%;
} }
#queue{
display: flex;
flex: 1;
flex-direction: row;
position: relative;
}
#queue-container{
position: relative;
flex-grow: 1;
}
#queue-marker-container{
display: flex;
flex-direction: column;
justify-content: space-between;
flex-grow: 0.1;
}
div.queue-marker{ div.queue-marker{
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
grid-column: 1;
z-index: 2; z-index: 2;
} }
@ -36,12 +54,29 @@ span.queue-marker{
div.queue-entry{ div.queue-entry{
overflow: hidden; overflow: hidden;
margin: 1.5em 0.5em; position: absolute;
grid-column: 2; margin: 0 1em;
right: 0;
left: 0;
}
div.queue-entry a{
position: relative;
z-index: 2;
} }
#time-marker{ #time-marker{
position: absolute; position: absolute;
height: 1px; height: 1px;
width: 100%; width: 100%;
z-index: 1;
}
#queue-marker-scale-label{
position: absolute;
top: 25%;
left: 0;
right: 0;
text-align: center;
font-size: 3em;
} }

View file

@ -47,6 +47,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
--danger-glow0: 2px 2px 3px var(--danger0), -2px 2px 3px var(--danger0), 2px -2px 3px var(--danger0), -2px -2px 3px var(--danger0); --danger-glow0: 2px 2px 3px var(--danger0), -2px 2px 3px var(--danger0), 2px -2px 3px var(--danger0), -2px -2px 3px var(--danger0);
--danger-glow0-alt1: 2px 2px 3px var(--danger0-alt1), -2px 2px 3px var(--danger0-alt1), 2px -2px 3px var(--danger0-alt1), -2px -2px 3px var(--danger0-alt1); --danger-glow0-alt1: 2px 2px 3px var(--danger0-alt1), -2px 2px 3px var(--danger0-alt1), 2px -2px 3px var(--danger0-alt1), -2px -2px 3px var(--danger0-alt1);
--timer-glow: -2px 1px 3px var(--danger0-alt1), 2px -1px 3px var(--danger0-alt1);
--userlist-color0:rgb(63, 121, 71); --userlist-color0:rgb(63, 121, 71);
--userlist-color1:rgb(143, 46, 26); --userlist-color1:rgb(143, 46, 26);
--userlist-color2:rgb(51, 101, 161); --userlist-color2:rgb(51, 101, 161);
@ -464,12 +466,13 @@ span.queue-marker{
#time-marker{ #time-marker{
background-color: var(--danger0); background-color: var(--danger0);
box-shadow: var(--timer-glow);
} }
div.queue-entry{ div.queue-entry{
margin: 0.2em; margin: 0.2em;
padding: 0 0.7em; padding: 0 0.7em;
border-radius: 1em; border-radius: 0.3em;
background-color: var(--bg1); background-color: var(--bg1);
border: 1px solid var(--accent1); border: 1px solid var(--accent1);
} }

View file

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

View file

@ -209,7 +209,7 @@ class player{
this.toggleUI(true); this.toggleUI(true);
clearTimeout(this.uiTimer); clearTimeout(this.uiTimer);
if(!this.onUI){ if(!this.onUI){
this.uiTimer = setTimeout(this.toggleUI.bind(this), 1500, false); //this.uiTimer = setTimeout(this.toggleUI.bind(this), 1500, false);
} }
} }

View file

@ -35,6 +35,20 @@ class canopyUtils{
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/escape */ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/escape */
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
} }
async awaitNextFrame(){
//return a new promise
return new Promise((resolve)=>{
//Before the next frame
requestAnimationFrame(()=>{
//fires before next-next frame (after next frame)
requestAnimationFrame(()=>{
//resolve the promise
resolve();
});
});
})
}
} }
class canopyUXUtils{ class canopyUXUtils{