Continued work on media schedule panel.

This commit is contained in:
rainbow napkin 2025-02-02 18:49:20 -05:00
parent d5a2a51be2
commit 90be85de26
6 changed files with 475 additions and 103 deletions

View file

@ -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 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/>.*/
//NPM imports
const validator = require('validator');
//Local imports //Local imports
const queuedMedia = require('./queuedMedia'); const queuedMedia = require('./queuedMedia');
const yanker = require('../../../utils/media/yanker'); const yanker = require('../../../utils/media/yanker');
@ -51,8 +54,37 @@ module.exports = class{
async queueURL(socket, data){ async queueURL(socket, data){
try{ try{
//Set url
const url = encodeURI(data.url);
//pull URL and start time from data //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 //Pull media list
const mediaList = await yanker.yankMedia(url, title); const mediaList = await yanker.yankMedia(url, title);
@ -86,6 +118,14 @@ module.exports = class{
deleteMedia(socket, data){ deleteMedia(socket, data){
try{ 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 //Remove media by UUID
this.removeMedia(data.uuid, socket); this.removeMedia(data.uuid, socket);
}catch(err){ }catch(err){
@ -95,6 +135,20 @@ module.exports = class{
moveMedia(socket, data){ moveMedia(socket, data){
try{ 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 //Move media by UUID
this.rescheduleMedia(data.uuid, data.start, socket); this.rescheduleMedia(data.uuid, data.start, socket);
}catch(err){ }catch(err){

View file

@ -19,11 +19,16 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
<div id="queue-control-buttons"> <div id="queue-control-buttons">
<button id="queue-add-media"><i class="bi-plus-lg"></i></button> <button id="queue-add-media"><i class="bi-plus-lg"></i></button>
<button id="queue-search-media"><i class="bi-search"></i></button> <button id="queue-search-media"><i class="bi-search"></i></button>
<button id="queue-scroll-lock" class="positive-button"><i class="bi-clock-fill"></i><i class="bi-arrows-expand"></i></button>
<button id="queue-date"><i class="bi-calendar-fill"></i></button>
<button id="queue-playlists"><i class="bi-list"></i></button> <button id="queue-playlists"><i class="bi-list"></i></button>
<button id="queue-scroll-lock" class="positive-button"><i class="bi-clock-fill"></i></button> <button id="queue-clear" class="danger-button"><i class="bi-trash-fill"></i></button>
<button id="queue-export-clear"><i class="bi-trash-fill"></i></button> <button id="queue-lock" class="positive-button"><i class="bi-unlock-fill"></i></button>
<button id="queue-export-date"><i class="bi-calendar-fill"></i></button> </div>
<button id="queue-export-lock" class="positive-button"><i class="bi-unlock-fill"></i></button> <div id="queue-control-date" style="display: none">
<i class="bi-caret-left" id="queue-control-date-decrement"></i>
<input id="queue-control-date-prompt" type="date">
<i class="bi-caret-right" id="queue-control-date-increment"></i>
</div> </div>
<div id="queue-media-prompts" style="display: none;"> <div id="queue-media-prompts" style="display: none;">
<div class="panel-control-prompt control-prompt"> <div class="panel-control-prompt control-prompt">

View file

@ -25,10 +25,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
left: 0; left: 0;
right: 0; right: 0;
z-index: 3; z-index: 3;
padding: 0.15em 0.3em;
} }
#queue-control-offset{ #queue-control-offset{
margin-bottom: 2em; margin-bottom: 2.15em;
} }
#queue-control-buttons{ #queue-control-buttons{
@ -73,6 +74,19 @@ span.queue-marker{
height: 1px; height: 1px;
min-width: 5px; min-width: 5px;
flex: 1; 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{ div.queue-entry{
@ -107,4 +121,19 @@ div.queue-entry a{
.media-tooltip p{ .media-tooltip p{
margin: 0; 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;
} }

View file

@ -88,16 +88,22 @@ a{
color: var(--accent0); color: var(--accent0);
} }
a:hover, i:hover, .interactive:hover{ /* NOT! -Wayne */
a:hover, i:hover:not(button i), .interactive:hover{
color: var(--focus0-alt0); color: var(--focus0-alt0);
text-shadow: var(--focus-glow0); text-shadow: var(--focus-glow0);
} }
a:active, i:active, .interactive:active{ a:active, i:active:not(button i), .interactive:active{
color: var(--focus0-alt1); color: var(--focus0-alt1);
text-shadow: var(--focus-glow0-alt0); text-shadow: var(--focus-glow0-alt0);
} }
button i{
margin: 0.05em;
text-wrap: nowrap;
}
select{ select{
background-color: var(--bg2); background-color: var(--bg2);
border-radius: 0.5em; border-radius: 0.5em;
@ -133,7 +139,6 @@ input:checked{
box-shadow: var(--focus-glow0); box-shadow: var(--focus-glow0);
} }
/* NOT! -Wayne */
input:not([type='checkbox']):not(.navbar-item), textarea { input:not([type='checkbox']):not(.navbar-item), textarea {
border-radius: 1em; border-radius: 1em;
border: none; border: none;
@ -428,7 +433,7 @@ div.tooltip{
border-radius: 1em; border-radius: 1em;
} }
/* panel */ /* panel containers */
.cpanel-body{ .cpanel-body{
background-image: none; background-image: none;
} }
@ -437,6 +442,8 @@ div.tooltip{
background-color: var(--accent0); background-color: var(--accent0);
} }
/* panels */
/* emote panel */
span.emote-panel-list-emote{ span.emote-panel-list-emote{
border: 1px solid var(--accent0); border: 1px solid var(--accent0);
} }
@ -460,6 +467,7 @@ span.emote-list-trash-icon{
border: 1px solid var(--accent0) border: 1px solid var(--accent0)
} }
/* queue panel */
#queue-controls{ #queue-controls{
background-color: var(--bg0-alpha1); background-color: var(--bg0-alpha1);
} }
@ -473,6 +481,8 @@ span.queue-marker{
box-shadow: var(--timer-glow); 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;
@ -491,6 +501,22 @@ div.queue-entry{
font-family: monospace; 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*/ /* altcha theming*/
div.altcha{ div.altcha{
box-shadow: 4px 4px 1px var(--bg1-alt0) inset; box-shadow: 4px 4px 1px var(--bg1-alt0) inset;

View file

@ -1,6 +1,12 @@
class queuePanel extends panelObj{ class queuePanel extends panelObj{
constructor(client, panelDocument){ 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 //Store releative scale of items in seconds, defaulting to 30 minute chunks
this.scale = 30 * 60; this.scale = 30 * 60;
@ -8,6 +14,7 @@ class queuePanel extends panelObj{
//Create variable to hold rescale timer //Create variable to hold rescale timer
this.rescaleTimer = null; this.rescaleTimer = null;
//Autoscroll boolean
this.autoscroll = true; this.autoscroll = true;
//Define non-input event listeners //Define non-input event listeners
@ -32,10 +39,11 @@ class queuePanel extends panelObj{
//Get main control buttons //Get main control buttons
this.addMediaButton = this.panelDocument.querySelector('#queue-add-media'); this.addMediaButton = this.panelDocument.querySelector('#queue-add-media');
this.scrollLockButton = this.panelDocument.querySelector('#queue-scroll-lock'); this.scrollLockButton = this.panelDocument.querySelector('#queue-scroll-lock');
this.queueDateButton = this.panelDocument.querySelector('#queue-date')
//Get control divs //Get control divs
//Add Media
this.addMediaDiv = this.panelDocument.querySelector('#queue-media-prompts'); this.addMediaDiv = this.panelDocument.querySelector('#queue-media-prompts');
this.queueDateDiv = this.panelDocument.querySelector('#queue-control-date');
//Get control div elements //Get control div elements
//Add Media //Add Media
@ -43,6 +51,10 @@ class queuePanel extends panelObj{
this.addMediaNamePrompt = this.panelDocument.querySelector('#media-name-input'); this.addMediaNamePrompt = this.panelDocument.querySelector('#media-name-input');
this.queueLastButton = this.panelDocument.querySelector('#queue-last-button'); this.queueLastButton = this.panelDocument.querySelector('#queue-last-button');
this.queueAtButton = this.panelDocument.querySelector('#queue-at-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 //Render out the queue
this.fullRender(); this.fullRender();
@ -73,33 +85,120 @@ class queuePanel extends panelObj{
//control bar controls //control bar controls
this.addMediaButton.addEventListener('click', this.toggleAddMedia.bind(this)); this.addMediaButton.addEventListener('click', this.toggleAddMedia.bind(this));
this.scrollLockButton.addEventListener('click', this.lockScroll.bind(this)); this.scrollLockButton.addEventListener('click', this.lockScroll.bind(this));
this.queueDateButton.addEventListener('click', this.toggleDateControl.bind(this));
//control bar divs //control bar divs
//Add Media
this.queueLastButton.addEventListener('click', this.queueLast.bind(this)) 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){ toggleAddMedia(event){
//If the div is hidden
if(this.addMediaDiv.style.display == 'none'){ if(this.addMediaDiv.style.display == 'none'){
//Light up the button
this.addMediaButton.classList.add('positive-button');
//Show the div
this.addMediaDiv.style.display = ''; this.addMediaDiv.style.display = '';
}else{ }else{
//Unlight the button
this.addMediaButton.classList.remove('positive-button');
//Hide the div
this.addMediaDiv.style.display = 'none'; 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){ lockScroll(event){
//Enable scroll lock //Enable scroll lock
this.autoscroll = true; this.autoscroll = true;
//Light the indicator
this.scrollLockButton.classList.add('positive-button'); //If we have a time marker
//Render the marker early to insta-jump 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(); 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){ scaleScroll(event){
@ -163,10 +262,13 @@ class queuePanel extends panelObj{
this.rescaleTimer = setTimeout(this.fullRender.bind(this), 500); this.rescaleTimer = setTimeout(this.fullRender.bind(this), 500);
//Otherwise, if we're just scrolling //Otherwise, if we're just scrolling
}else{ }else{
//Disable scroll lock //If we're looking at today
this.autoscroll = false; if(utils.isSameDate(new Date(), this.day)){
//Unlight the indicator //Disable scroll lock
this.scrollLockButton.classList.remove('positive-button'); 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 //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++){ for(let i = 0; i <= 2; i++){
await utils.awaitNextFrame(); await utils.ux.awaitNextFrame();
} }
//render the time marker //render the time marker
@ -292,79 +394,188 @@ class queuePanel extends panelObj{
//for every entry in the queue //for every entry in the queue
for(let entry of this.client.queue){ for(let entry of this.client.queue){
//Create entry div //Check if item starts today
const entryDiv = document.createElement('div'); const startsToday = utils.isSameDate(this.day, new Date(entry[1].startTime));
entryDiv.classList.add('queue-entry'); //Check if item ends today
const endsToday = utils.isSameDate(this.day, new Date(entry[1].startTime + (entry[1].duration * 1000)));
//Place entry div //If the item either starts or ends today
entryDiv.style.top = `${this.offsetByDate(new Date(entry[1].startTime))}px`; var playsToday = (startsToday || endsToday);
entryDiv.style.bottom = `${this.offsetByDate(new Date(entry[1].startTime + (entry[1].duration * 1000)), true)}px`;
//Create entry title //If the media neither starts nor ends today
const entryTitle = document.createElement('p'); if(!playsToday){
entryTitle.textContent = entry[1].title; //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);
//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 //If part of the current item plays today
entryDiv.addEventListener('mouseenter',(event)=>{utils.ux.displayTooltip(event, tooltipDiv, false, null, true, this.ownerDoc);}); if(playsToday){
//Create entry div
const entryDiv = document.createElement('div');
entryDiv.classList.add('queue-entry');
//context menu //If this item starts today
const menuMap = new Map([ if(startsToday){
["Play now", ()=>{this.client.socket.emit('move', {uuid: entry[1].uuid})}], //Place entry div based on time
["Move To...", ()=>{}], entryDiv.style.top = `${this.offsetByDate(new Date(entry[1].startTime))}px`;
["Delete", ()=>{this.client.socket.emit('delete', {uuid: entry[1].uuid})}], }else{
["Open in New Tab", ()=>{window.open(entry[1].url, '_blank').focus();}], //Get dawn
["Copy URL", ()=>{navigator.clipboard.writeText(entry[1].url);}], const dawn = new Date();
]); dawn.setHours(0,0,0,0);
//Setup context menu //Place item beginning at dawn
entryDiv.addEventListener('contextmenu', (event)=>{utils.ux.displayContextMenu(event, '', menuMap, this.ownerDoc);}); entryDiv.style.top = `${this.offsetByDate(dawn)}px`;
//Append entry elements
entryDiv.append(entryTitle);
//Append entry //Run entry from top
this.queueContainer.append(entryDiv); 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))(); (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 we need to make the time marker
if(this.timeMarker == null){ if(this.timeMarker == null){
//Create the time marker //Create the time marker
@ -386,7 +609,7 @@ class queuePanel extends panelObj{
this.timeMarker.id = 'time-marker'; this.timeMarker.id = 'time-marker';
//Append time marker //Append time marker
this.queue.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`;
@ -433,6 +656,9 @@ class queuePanel extends panelObj{
//Store epoch of current date at midnight for later user //Store epoch of current date at midnight for later user
const dateEpoch = date.getTime(); 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 //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){ for(let time = 0; time <= 86400; time += this.scale){
//Get index of current chunk by dividing time by scale //Get index of current chunk by dividing time by scale
@ -442,8 +668,8 @@ class queuePanel extends panelObj{
date.setTime(dateEpoch + (time * 1000)) date.setTime(dateEpoch + (time * 1000))
//Create marker span //Create marker span
const markerSpan = document.createElement('div'); const markerDiv = document.createElement('div');
markerSpan.classList.add('queue-marker'); markerDiv.classList.add('queue-marker');
//Create marker line (unfortunately <hr> tags don't seem to play nice with parents who have display:flex) //Create marker line (unfortunately <hr> tags don't seem to play nice with parents who have display:flex)
const marker = document.createElement('span'); const marker = document.createElement('span');
@ -470,14 +696,25 @@ class queuePanel extends panelObj{
} }
//Add marker label to marker span //Add marker label to marker span
markerSpan.appendChild(markerLabel); markerDiv.appendChild(markerLabel);
} }
//Append marker to marker span //Append marker to marker span
markerSpan.appendChild(marker); markerDiv.appendChild(marker);
//Append marker span to queue container //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');
} }
} }

View file

@ -36,6 +36,20 @@ class canopyUtils{
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); 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(){ async awaitNextFrame(){
//return a new promise //return a new promise
return new Promise((resolve)=>{ return new Promise((resolve)=>{
@ -49,11 +63,18 @@ class canopyUtils{
}); });
}) })
} }
}
class canopyUXUtils{ //Useful for pesky date/time input conversions
constructor(){ 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 //Update this and popup class to use nodes
//and display multiple errors in one popup //and display multiple errors in one popup