diff --git a/src/views/partial/panels/queue.ejs b/src/views/partial/panels/queue.ejs
index 08043fe..e7b5ed5 100644
--- a/src/views/partial/panels/queue.ejs
+++ b/src/views/partial/panels/queue.ejs
@@ -14,17 +14,21 @@ 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 . %>
-
-
-
-
-
-
- <%# Probably not the cleanest way to do this but fuggit %>
-
-
-
-
-
-
+
+
+
+
+
+
+
+ <%# Probably not the cleanest way to do this but fuggit %>
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/www/css/channel.css b/www/css/channel.css
index f959e32..3be0ab3 100644
--- a/www/css/channel.css
+++ b/www/css/channel.css
@@ -242,14 +242,12 @@ span.user-entry{
top: 0;
bottom: 0;
left: 0;
- overflow-y: auto;
scrollbar-width: thin;
width: 75%;
}
#cpanel-pinned-div{
position: relative;
- overflow-y: auto;
scrollbar-width: thin;
}
diff --git a/www/css/panel.css b/www/css/panel.css
index a85d2ff..b99a4d6 100644
--- a/www/css/panel.css
+++ b/www/css/panel.css
@@ -16,6 +16,7 @@ along with this program. If not, see .*/
.cpanel-div{
flex-direction: column;
overflow: hidden;
+ flex: 1;
}
.cpanel-header-div{
@@ -37,4 +38,19 @@ along with this program. If not, see .*/
.cpanel-header-icon{
cursor: pointer;
+}
+
+.cpanel-doc{
+ flex: 1;
+ overflow: auto;
+}
+
+.cpanel-body{
+ display: block;
+ height: 100%;
+}
+
+#cpanel-div{
+ display: flex;
+ height: 100%;
}
\ No newline at end of file
diff --git a/www/css/panel/queue.css b/www/css/panel/queue.css
index d9d370f..d7f9410 100644
--- a/www/css/panel/queue.css
+++ b/www/css/panel/queue.css
@@ -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
along with this program. If not, see .*/
-#queue{
- position: relative;
- display: grid;
- grid-template-columns: auto 75%;
+#queue-panel-layout-controller{
+ display: flex;
+ flex-direction: column;
+ 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{
display: flex;
flex-direction: row;
align-items: center;
- grid-column: 1;
z-index: 2;
}
@@ -36,12 +54,29 @@ span.queue-marker{
div.queue-entry{
overflow: hidden;
- margin: 1.5em 0.5em;
- grid-column: 2;
+ position: absolute;
+ margin: 0 1em;
+ right: 0;
+ left: 0;
+}
+
+div.queue-entry a{
+ position: relative;
+ z-index: 2;
}
#time-marker{
position: absolute;
height: 1px;
width: 100%;
+ z-index: 1;
+}
+
+#queue-marker-scale-label{
+ position: absolute;
+ top: 25%;
+ left: 0;
+ right: 0;
+ text-align: center;
+ font-size: 3em;
}
\ No newline at end of file
diff --git a/www/css/theme/movie-night.css b/www/css/theme/movie-night.css
index bb32e30..b4c66fb 100644
--- a/www/css/theme/movie-night.css
+++ b/www/css/theme/movie-night.css
@@ -47,6 +47,8 @@ along with this program. If not, see .*/
--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);
+ --timer-glow: -2px 1px 3px var(--danger0-alt1), 2px -1px 3px var(--danger0-alt1);
+
--userlist-color0:rgb(63, 121, 71);
--userlist-color1:rgb(143, 46, 26);
--userlist-color2:rgb(51, 101, 161);
@@ -464,12 +466,13 @@ span.queue-marker{
#time-marker{
background-color: var(--danger0);
+ box-shadow: var(--timer-glow);
}
div.queue-entry{
margin: 0.2em;
padding: 0 0.7em;
- border-radius: 1em;
+ border-radius: 0.3em;
background-color: var(--bg1);
border: 1px solid var(--accent1);
}
diff --git a/www/js/channel/panels/queuePanel.js b/www/js/channel/panels/queuePanel.js
index e73133b..faf4ec6 100644
--- a/www/js/channel/panels/queuePanel.js
+++ b/www/js/channel/panels/queuePanel.js
@@ -4,11 +4,178 @@ class queuePanel extends panelObj{
//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;
+
+ //Define non-input event listeners
+ this.defineListeners();
}
docSwitch(){
+ //Get queue div
+ this.queue = this.panelDocument.querySelector('#queue');
//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: ${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(this.timeMarker != null){
@@ -16,28 +183,46 @@ class queuePanel extends panelObj{
this.timeMarker.remove();
this.timeMarker = null;
}
-
- //Render out the queue
- this.renderQueue();
}
- closer(){
- //Clear any remaining timers
- clearTimeout(this.renderTimeMarker);
- }
+ async fullRender(date = new Date()){
- renderQueue(date = new Date()){
- //Clear out queue container
- this.queueContainer.innerHTML = '';
+ //Clear the queue
+ this.clearQueue();
+
+ //If we have a scale label
+ if(this.scaleLabel != null){
+ //Take it out
+ this.scaleLabel.remove();
+ this.scaleLabel = null;
+ }
//Render out time scale
this.renderQueueScale(date);
+ //wait a few frames so the scale can finish rendering, because dom function aren't async for some fucking reason
+ for(let i = 0; i <= 2; i++){
+ await utils.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){
//Create entry div
const entryDiv = document.createElement('div');
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
const entryTitle = document.createElement('a');
entryTitle.textContent = entry[1].title;
@@ -52,42 +237,50 @@ class queuePanel extends panelObj{
}
renderTimeMarker(date = new Date()){
- //Pull start of day epoch from given date
- const dayEpoch = structuredClone(date).setHours(0,0,0,0);
- //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');
+ //Calculate marker position by date
+ const markerPosition = Math.round(this.offsetByDate(date));
- //If the marker is null for some reason
- if(markers[0] == null){
+ //If markers are null
+ if(markerPosition == null){
//Try again in a second since the user probably just popped the panel or something :P
(smackTimer.bind(this))();
- 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(this.timeMarker == null){
- //Create time marker
+ //Create the time marker
this.timeMarker = document.createElement('span');
//Add time marker class
this.timeMarker.id = 'time-marker';
//Append time marker
- this.queueContainer.appendChild(this.timeMarker);
+ 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){
+ //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))();
function smackTimer(){
@@ -102,7 +295,7 @@ class queuePanel extends panelObj{
//Make sure we don't modify the date we're handed
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);
//Store epoch of current date at midnight for later user
@@ -152,18 +345,32 @@ class queuePanel extends panelObj{
markerSpan.appendChild(marker);
//Append marker span to queue container
- this.queueContainer.appendChild(markerSpan);
+ this.queueMarkerContainer.appendChild(markerSpan);
+ }
+ }
+
+ offsetByDate(date = new Date(), bottomOffset = false){
+ //Pull start of day epoch from given date
+ const dayEpoch = structuredClone(date).setHours(0,0,0,0);
+ //Get difference between now and day epoch to get time since the start of the current day in milliseconds
+ const curTime = date.getTime() - dayEpoch;
+ //Devide by amount of milliseconds in a day to convert time over to a floating point number between 0 and 1
+ //Make sure to reverse it if bottomOffset is set to true
+ const timeFloat = bottomOffset ? 1 - (curTime / 86400000) : curTime / 86400000;
+ //Get queue markers
+ const markers = this.panelDocument.querySelectorAll('span.queue-marker');
+
+ //If the marker is null for some reason
+ if(markers[0] == null){
+
+ return null;
}
- //Easiest way to wait for DOM to do it's thing is to:
- //fires before next frame
- requestAnimationFrame(()=>{
- //fires before next-next frame (after next frame)
- requestAnimationFrame(()=>{
- //render the time marker
- this.renderTimeMarker(inputDate);
- });
- });
-
+ //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];
}
}
\ No newline at end of file
diff --git a/www/js/channel/player.js b/www/js/channel/player.js
index adeb85e..9616bd7 100644
--- a/www/js/channel/player.js
+++ b/www/js/channel/player.js
@@ -209,7 +209,7 @@ class player{
this.toggleUI(true);
clearTimeout(this.uiTimer);
if(!this.onUI){
- this.uiTimer = setTimeout(this.toggleUI.bind(this), 1500, false);
+ //this.uiTimer = setTimeout(this.toggleUI.bind(this), 1500, false);
}
}
diff --git a/www/js/utils.js b/www/js/utils.js
index 12a3750..bb415d5 100644
--- a/www/js/utils.js
+++ b/www/js/utils.js
@@ -35,6 +35,20 @@ class canopyUtils{
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/escape */
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{