diff --git a/src/app/channel/media/queue.js b/src/app/channel/media/queue.js
index e591858..7e6b9f2 100644
--- a/src/app/channel/media/queue.js
+++ b/src/app/channel/media/queue.js
@@ -14,6 +14,9 @@ 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 .*/
+//NPM imports
+const validator = require('validator');
+
//Local imports
const queuedMedia = require('./queuedMedia');
const yanker = require('../../../utils/media/yanker');
@@ -51,8 +54,37 @@ module.exports = class{
async queueURL(socket, data){
try{
+ //Set url
+ const url = encodeURI(data.url);
+
//pull URL and start time from data
- let {url, start, title} = data;
+ //let {url, start, title} = data;
+ //If we where given a bad URL
+ if(!validator.isURL(url)){
+ //Bitch, moan, complain...
+ loggerUtils.socketErrorHandler(socket, "Bad URL!", "validation");
+ //and ignore it!
+ return;
+ }
+
+ //If the title is too long
+ if(!validator.isLength(data.title, {max:30})){
+ //Bitch, moan, complain...
+ loggerUtils.socketErrorHandler(socket, "Title too long!", "validation");
+ //and ignore it!
+ return;
+ }
+
+ //Set title
+ const title = validator.escape(validator.trim(data.title));
+ //set start
+ var start = data.start;
+
+ //If start time isn't an integer after the current epoch
+ if(start != null &&!validator.isInt(String(start), (new Date().getTime()))){
+ //Null out time to tell the later parts of the function to start it now
+ start = null;
+ }
//Pull media list
const mediaList = await yanker.yankMedia(url, title);
@@ -86,6 +118,14 @@ module.exports = class{
deleteMedia(socket, data){
try{
+ //If we don't have a valid UUID
+ if(!validator.isUUID(data.uuid)){
+ //Bitch, moan, complain...
+ loggerUtils.socketErrorHandler(socket, "Bad UUID!", "validation");
+ //and ignore it!
+ return;
+ }
+
//Remove media by UUID
this.removeMedia(data.uuid, socket);
}catch(err){
@@ -95,6 +135,20 @@ module.exports = class{
moveMedia(socket, data){
try{
+ //If we don't have a valid UUID
+ if(!validator.isUUID(data.uuid)){
+ //Bitch, moan, complain...
+ loggerUtils.socketErrorHandler(socket, "Bad UUID!", "validation");
+ //and ignore it!
+ return;
+ }
+
+ //If start time isn't an integer after the current epoch
+ if(data.start != null && !validator.isInt(String(data.start), new Date().getTime())){
+ //Null out time to tell the later parts of the function to start it now
+ data.start = null;
+ }
+
//Move media by UUID
this.rescheduleMedia(data.uuid, data.start, socket);
}catch(err){
diff --git a/src/views/partial/panels/queue.ejs b/src/views/partial/panels/queue.ejs
index c52e8aa..470ac2b 100644
--- a/src/views/partial/panels/queue.ejs
+++ b/src/views/partial/panels/queue.ejs
@@ -19,11 +19,16 @@ along with this program. If not, see . %>
diff --git a/www/css/panel/queue.css b/www/css/panel/queue.css
index b6cb460..e28a2b9 100644
--- a/www/css/panel/queue.css
+++ b/www/css/panel/queue.css
@@ -25,10 +25,11 @@ along with this program. If not, see .*/
left: 0;
right: 0;
z-index: 3;
+ padding: 0.15em 0.3em;
}
#queue-control-offset{
- margin-bottom: 2em;
+ margin-bottom: 2.15em;
}
#queue-control-buttons{
@@ -73,6 +74,19 @@ span.queue-marker{
height: 1px;
min-width: 5px;
flex: 1;
+ margin: 0.8em 0;
+}
+
+.queue-marker p{
+ margin: 0
+}
+
+div.queue-marker-first span{
+ margin: 0 0 0.8em 0;
+}
+
+div.queue-marker-last span{
+ margin: 0.8em 0 0 0;
}
div.queue-entry{
@@ -107,4 +121,19 @@ div.queue-entry a{
.media-tooltip p{
margin: 0;
+}
+
+/* queue controls */
+/* date */
+#queue-control-date{
+ display: flex;
+ justify-content: center;
+ margin: 0.3em 20%;
+}
+
+#queue-control-date input{
+ width: 100%;
+ min-width: fit-content;
+ padding: 0 0.2em;
+ text-align: center;
}
\ No newline at end of file
diff --git a/www/css/theme/movie-night.css b/www/css/theme/movie-night.css
index b17b3ce..5c60777 100644
--- a/www/css/theme/movie-night.css
+++ b/www/css/theme/movie-night.css
@@ -88,16 +88,22 @@ a{
color: var(--accent0);
}
-a:hover, i:hover, .interactive:hover{
+/* NOT! -Wayne */
+a:hover, i:hover:not(button i), .interactive:hover{
color: var(--focus0-alt0);
text-shadow: var(--focus-glow0);
}
-a:active, i:active, .interactive:active{
+a:active, i:active:not(button i), .interactive:active{
color: var(--focus0-alt1);
text-shadow: var(--focus-glow0-alt0);
}
+button i{
+ margin: 0.05em;
+ text-wrap: nowrap;
+}
+
select{
background-color: var(--bg2);
border-radius: 0.5em;
@@ -133,7 +139,6 @@ input:checked{
box-shadow: var(--focus-glow0);
}
-/* NOT! -Wayne */
input:not([type='checkbox']):not(.navbar-item), textarea {
border-radius: 1em;
border: none;
@@ -428,7 +433,7 @@ div.tooltip{
border-radius: 1em;
}
-/* panel */
+/* panel containers */
.cpanel-body{
background-image: none;
}
@@ -437,6 +442,8 @@ div.tooltip{
background-color: var(--accent0);
}
+/* panels */
+/* emote panel */
span.emote-panel-list-emote{
border: 1px solid var(--accent0);
}
@@ -460,6 +467,7 @@ span.emote-list-trash-icon{
border: 1px solid var(--accent0)
}
+/* queue panel */
#queue-controls{
background-color: var(--bg0-alpha1);
}
@@ -473,6 +481,8 @@ span.queue-marker{
box-shadow: var(--timer-glow);
}
+
+
div.queue-entry{
margin: 0.2em;
padding: 0 0.7em;
@@ -491,6 +501,22 @@ div.queue-entry{
font-family: monospace;
}
+#queue-control-buttons button:not(:hover, .danger-button, .positive-button){
+ background-color: var(--bg2-alt1);
+}
+
+div.started-before-today{
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+ border-top: 1px dashed var(--accent1);
+}
+
+div.ends-after-today{
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
+ border-bottom: 1px dashed var(--accent1);
+}
+
/* altcha theming*/
div.altcha{
box-shadow: 4px 4px 1px var(--bg1-alt0) inset;
diff --git a/www/js/channel/panels/queuePanel.js b/www/js/channel/panels/queuePanel.js
index 2a3a9fa..4f8ddb2 100644
--- a/www/js/channel/panels/queuePanel.js
+++ b/www/js/channel/panels/queuePanel.js
@@ -1,6 +1,12 @@
class queuePanel extends panelObj{
constructor(client, panelDocument){
- super(client, "Media Queue", "/panel/queue", 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;
@@ -8,6 +14,7 @@ class queuePanel extends panelObj{
//Create variable to hold rescale timer
this.rescaleTimer = null;
+ //Autoscroll boolean
this.autoscroll = true;
//Define non-input event listeners
@@ -32,10 +39,11 @@ class queuePanel extends panelObj{
//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')
//Get control divs
- //Add Media
this.addMediaDiv = this.panelDocument.querySelector('#queue-media-prompts');
+ this.queueDateDiv = this.panelDocument.querySelector('#queue-control-date');
//Get control div elements
//Add Media
@@ -43,6 +51,10 @@ class queuePanel extends panelObj{
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');
//Render out the queue
this.fullRender();
@@ -73,33 +85,120 @@ class queuePanel extends panelObj{
//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));
//control bar divs
+ //Add Media
this.queueLastButton.addEventListener('click', this.queueLast.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));
}
+ /* 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';
}
}
- 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
+
+ //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';
+ }
+ }
+
+ /* add queue controls */
+ 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 = '';
+ }
+
+ /* 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){
+ //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();
+ }
}
scaleScroll(event){
@@ -163,10 +262,13 @@ class queuePanel extends panelObj{
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');
+ //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');
+ }
}
}
@@ -276,7 +378,7 @@ class queuePanel extends panelObj{
//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();
+ await utils.ux.awaitNextFrame();
}
//render the time marker
@@ -292,79 +394,188 @@ class queuePanel extends panelObj{
//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');
+ //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)));
- //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`;
+ //If the item either starts or ends today
+ var playsToday = (startsToday || endsToday);
- //Create entry title
- const entryTitle = document.createElement('p');
- entryTitle.textContent = entry[1].title;
-
- //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);
+ //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);
}
- //Setup media tooltip
- entryDiv.addEventListener('mouseenter',(event)=>{utils.ux.displayTooltip(event, tooltipDiv, false, null, true, this.ownerDoc);});
+ //If part of the current item plays today
+ if(playsToday){
+ //Create entry div
+ const entryDiv = document.createElement('div');
+ entryDiv.classList.add('queue-entry');
- //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);}],
- ]);
+ //If this item starts today
+ if(startsToday){
+ //Place 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);
- //Setup context menu
- entryDiv.addEventListener('contextmenu', (event)=>{utils.ux.displayContextMenu(event, '', menuMap, this.ownerDoc);});
-
- //Append entry elements
- entryDiv.append(entryTitle);
+ //Place item beginning at dawn
+ entryDiv.style.top = `${this.offsetByDate(dawn)}px`;
- //Append entry
- this.queueContainer.append(entryDiv);
+ //Run entry from top
+ entryDiv.classList.add('started-before-today');
+ }
+
+ //If the item ends today
+ if(endsToday){
+ //Place entry div based on time
+ entryDiv.style.bottom = `${this.offsetByDate(new Date(entry[1].startTime + (entry[1].duration * 1000)), 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 entry to bottom
+ entryDiv.classList.add('ends-after-today');
+ }
+
+ //Create entry title
+ const entryTitle = document.createElement('p');
+ entryTitle.textContent = entry[1].title;
+
+ //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 = `End 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...", (event)=>{new reschedulePopup(event, this.client, entry[1])}],
+ ["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);
+
+ //Append entry
+ this.queueContainer.append(entryDiv);
+ }
+ }
+
+ 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();
+ }
+ }
}
}
@@ -378,6 +589,18 @@ class queuePanel extends panelObj{
(smackTimer.bind(this))();
}
+ //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
@@ -386,7 +609,7 @@ class queuePanel extends panelObj{
this.timeMarker.id = 'time-marker';
//Append time marker
this.queue.appendChild(this.timeMarker);
- }
+ }
//Set time marker position
this.timeMarker.style.top = `${markerPosition}px`;
@@ -433,6 +656,9 @@ class queuePanel extends panelObj{
//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
@@ -442,8 +668,8 @@ class queuePanel extends panelObj{
date.setTime(dateEpoch + (time * 1000))
//Create marker span
- const markerSpan = document.createElement('div');
- markerSpan.classList.add('queue-marker');
+ const markerDiv = document.createElement('div');
+ markerDiv.classList.add('queue-marker');
//Create marker line (unfortunately
tags don't seem to play nice with parents who have display:flex)
const marker = document.createElement('span');
@@ -470,14 +696,25 @@ class queuePanel extends panelObj{
}
//Add marker label to marker span
- markerSpan.appendChild(markerLabel);
+ markerDiv.appendChild(markerLabel);
}
//Append marker to marker span
- markerSpan.appendChild(marker);
+ markerDiv.appendChild(marker);
//Append marker span to queue container
- this.queueMarkerContainer.appendChild(markerSpan);
+ 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');
}
}
diff --git a/www/js/utils.js b/www/js/utils.js
index d899d2d..eb3cbe6 100644
--- a/www/js/utils.js
+++ b/www/js/utils.js
@@ -36,6 +36,20 @@ class canopyUtils{
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
+ isSameDate(d0, d1){
+ return d0.getYear() == d1.getYear() && d0.getMonth() == d1.getMonth() && d0.getDate() == d1.getDate();
+ }
+
+ dateWithinRange(min, max, input){
+ return min.getTime() < input.getTime() && max.getTime() > input.getTime();
+ }
+
+}
+
+class canopyUXUtils{
+ constructor(){
+ }
+
async awaitNextFrame(){
//return a new promise
return new Promise((resolve)=>{
@@ -49,11 +63,18 @@ class canopyUtils{
});
})
}
-}
-class canopyUXUtils{
- constructor(){
- }
+ //Useful for pesky date/time input conversions
+ localizeDate(date){
+ //Apply timezone offset to base (utc) time
+ return new Date(date.getTime() - (date.getTimezoneOffset() * 60000));
+ }
+
+ //Useful for pesky date/time input conversions
+ normalizeDate(date){
+ //Convert dates which store local time to utc to dates which store utc as utc
+ return new Date(date.getTime() + (date.getTimezoneOffset() * 60000));
+ }
//Update this and popup class to use nodes
//and display multiple errors in one popup