Continued work on media schedule panel.
This commit is contained in:
parent
d5a2a51be2
commit
90be85de26
6 changed files with 475 additions and 103 deletions
|
|
@ -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 <hr> 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');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue