Compare commits

..

No commits in common. "3ec770ba28076e8beb158c806d35a9129dda77ce" and "d0ef60dd4fc2fada77c0d7c6cbd68f5490e5f36c" have entirely different histories.

10 changed files with 77 additions and 326 deletions

View file

@ -106,8 +106,7 @@ class activeChannel{
"Greetings, %UNAME%.", "Greetings, %UNAME%.",
"Sup, %UNAME%.", "Sup, %UNAME%.",
"I AM THE GOD OF HELLFIRE, AND I BRING YOU: %UNAME%!", "I AM THE GOD OF HELLFIRE, AND I BRING YOU: %UNAME%!",
"A wild %UNAME% has appeared!", "A wild %UNAME% has appeared!"
"OH MY FUCKING GOD HOLY SHIT IT'S %UNAME%!!!",
], ],
leave: [ leave: [
"%UNAME% left.", "%UNAME% left.",
@ -135,6 +134,7 @@ class activeChannel{
"That'll do, %UNAME%." "That'll do, %UNAME%."
] ]
} }
} }
@ -150,8 +150,6 @@ class activeChannel{
var userObj = this.userList.get(userDB.user); var userObj = this.userList.get(userDB.user);
//get channel rank for current user //get channel rank for current user
const chanRank = await chanDB.getChannelRankByUserDoc(userDB); const chanRank = await chanDB.getChannelRankByUserDoc(userDB);
//Keep track of whether or not user has connected to this channel on another tab/device yet
let first = false;
//If user is already connected //If user is already connected
if(userObj){ if(userObj){
@ -163,8 +161,6 @@ class activeChannel{
await userDB.populate('flair'); await userDB.populate('flair');
//Set user object //Set user object
userObj = new connectedUser(userDB, chanRank, this, socket); userObj = new connectedUser(userDB, chanRank, this, socket);
//Throw first flag
first = true;
} }
//Set user entry in userlist //Set user entry in userlist
@ -179,7 +175,7 @@ class activeChannel{
this.webRTCHandler.defineListeners(socket); this.webRTCHandler.defineListeners(socket);
//Hand off the connection initiation to it's user object //Hand off the connection initiation to it's user object
const activeUser = await userObj.handleConnection(userDB, chanDB, socket, first) const activeUser = await userObj.handleConnection(userDB, chanDB, socket)
//Send out the userlist //Send out the userlist
this.broadcastUserList(socket.chan); this.broadcastUserList(socket.chan);
@ -271,7 +267,7 @@ class activeChannel{
const activeList = leaving ? this.userAnnouncements.leave : this.userAnnouncements.join; const activeList = leaving ? this.userAnnouncements.leave : this.userAnnouncements.join;
//Randomly yoink the applicable message //Randomly yoink the applicable message
const msg = activeList[Math.floor(Math.random() * activeList.length)]; const msg = activeList[Math.floor(Math.random() * activeList.length)];
//Replace %UNAME% placeholder with users name and broadcast the welcome wagon
this.server.chatHandler.relayChat(user, "", 0, msg.replace("%UNAME%", user), "serverwhisper", this.name, []); this.server.chatHandler.relayChat(user, "", 0, msg.replace("%UNAME%", user), "serverwhisper", this.name, []);
} }
} }

View file

@ -96,12 +96,11 @@ class connectedUser{
* @param {Mongoose.Document} userDB - User Document Passthrough to save on DB Access * @param {Mongoose.Document} userDB - User Document Passthrough to save on DB Access
* @param {Mongoose.Document} chanDB - Channnel Document Passthrough to save on DB Access * @param {Mongoose.Document} chanDB - Channnel Document Passthrough to save on DB Access
* @param {Socket} socket - Requesting Socket * @param {Socket} socket - Requesting Socket
* @param {Boolean} first - thrown true if this is the socket for this user on this channel
* @returns {activeUser} active user object generated by the new connection * @returns {activeUser} active user object generated by the new connection
*/ */
async handleConnection(userDB, chanDB, socket, first){ async handleConnection(userDB, chanDB, socket){
//send metadata to client //send metadata to client
this.sendClientMetadata(undefined, undefined, first); this.sendClientMetadata();
//Send out emotes //Send out emotes
this.sendSiteEmotes(); this.sendSiteEmotes();
@ -123,6 +122,7 @@ class connectedUser{
await userDB.tattooIPRecord(socket.handshake.address); await userDB.tattooIPRecord(socket.handshake.address);
} }
//Return active user object for use by activeChannel and channelManager objects //Return active user object for use by activeChannel and channelManager objects
return this; return this;
} }
@ -179,9 +179,8 @@ class connectedUser{
* Sends glut of required initial metadata to the client upon a new connection * Sends glut of required initial metadata to the client upon a new connection
* @param {Mongoose.Document} userDB - User Document Passthrough to save on DB Access * @param {Mongoose.Document} userDB - User Document Passthrough to save on DB Access
* @param {Mongoose.Document} chanDB - Channnel Document Passthrough to save on DB Access * @param {Mongoose.Document} chanDB - Channnel Document Passthrough to save on DB Access
* @param {Boolean} welcome - Whether or not to throw welcome wagon, ensures we dont welcome before the metadata is sent
*/ */
async sendClientMetadata(userDB, chanDB, welcome){ async sendClientMetadata(userDB, chanDB){
//Get flairList from DB and setup flairList array //Get flairList from DB and setup flairList array
const flairListDB = await flairModel.find({}); const flairListDB = await flairModel.find({});
var flairList = []; var flairList = [];
@ -239,11 +238,8 @@ class connectedUser{
//Send off the metadata to our user's clients //Send off the metadata to our user's clients
this.emit("clientMetadata", {user: userObj, flairList, queueLock, chatBuffer, streamType: chanDB.settings.streamType}); this.emit("clientMetadata", {user: userObj, flairList, queueLock, chatBuffer, streamType: chanDB.settings.streamType});
//If we where told to welcome the user //Broadcast the welcome wagon
if(welcome){ this.channel.welcomeWagon(userDB.user);
//Broadcast the welcome wagon
this.channel.welcomeWagon(userDB.user);
}
} }
/** /**

View file

@ -518,6 +518,29 @@ class queue{
*/ */
async getArchive(socket, data){ async getArchive(socket, data){
try{ try{
//If we get an invalid start date
if(typeof data.startDate != "number" || data.startDate < 0){
//Bitch, moan, an' complain!
loggerUtils.socketErrorHandler(socket, "Invalid Start Date!", "validation");
//Yeetus-Deletus bitch
return;
}
//If we get an invalid start date
if(typeof data.endDate != "number" || data.endDate < 0){
//Bitch, moan, an' complain!
loggerUtils.socketErrorHandler(socket, "Invalid End Date!", "validation");
//Yeetus-Deletus bitch
return;
}
//If our startDate isn't before our endDate
if(data.startDate >= data.endDate){
//Bitch, moan, an' complain!
loggerUtils.socketErrorHandler(socket, "Invalid Date Range!", "validation");
//Yeetus-Deletus bitch
return;
}
//Get the current channel from the database //Get the current channel from the database
const chanDB = await channelModel.findOne({name: socket.chan}); const chanDB = await channelModel.findOne({name: socket.chan});
@ -528,86 +551,12 @@ class queue{
return; return;
} }
let startDate = undefined; //Create new date objects to represent range of data requested
let endDate = undefined; const startDate = new Date(data.startDate);
let title = undefined; const endDate = new Date(data.endDate);
let url = undefined
//If we where supplied with a start date //Grab archives from given range, and send them back to the requesting socket
if(data.startDate != null){ socket.emit("archive", {archive: await this.prepArchive(startDate, endDate)});
//If we get an invalid start date
if((typeof data.startDate != "number" || data.startDate < 0)){
//Bitch, moan, an' complain!
loggerUtils.socketErrorHandler(socket, "Invalid Start Date!", "validation");
//Yeetus-Deletus bitch
return;
}
//Store validated start date as new date object
startDate = new Date(data.startDate);
}
if(data.endDate != null){
//If we get an invalid start date
if((typeof data.endDate != "number" || data.endDate < 0)){
//Bitch, moan, an' complain!
loggerUtils.socketErrorHandler(socket, "Invalid End Date!", "validation");
//Yeetus-Deletus bitch
return;
}
//If our startDate isn't before our endDate
if(data.startDate != null && data.startDate >= data.endDate){
//Bitch, moan, an' complain!
loggerUtils.socketErrorHandler(socket, "Invalid Date Range!", "validation");
//Yeetus-Deletus bitch
return;
}
//Store validated endDate as new date object
endDate = new Date(data.endDate);
}
//If we have a valid title
if(data.title != null && data.title != ''){
//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;
}
//Sanatize, trim, and keep the title
title = validator.escape(validator.trim(data.title));
}
//If we were given a URL
if(data.url != null && data.url != ''){
//Set URL
url = data.url;
//If we were given a bad URL
if(!validator.isURL(url,{require_valid_protocol: true})){
//Attempt to fix the situation by encoding it
url = encodeURI(url);
//If it's still bad
if(!validator.isURL(url,{require_valid_protocol: true})){
//Bitch, moan, complain...
loggerUtils.socketErrorHandler(socket, "Bad URL!", "validation");
//and ignore it!
return;
}
}
}
//Grab archives from given range, and send them back to the requesting socket, providing result as search result if search was requested
socket.emit(data.search ? "archiveSearch" : "archive", {archive: await this.prepArchive(startDate, endDate, title, url)});
}catch(err){ }catch(err){
return loggerUtils.socketExceptionHandler(socket, err); return loggerUtils.socketExceptionHandler(socket, err);
} }
@ -1954,20 +1903,12 @@ class queue{
} }
} }
/** async prepArchive(startDate, endDate = new Date()){
* Preps archive for network transmission to clients
* @param {Date} startDate - Start date to pass to archive query
* @param {Date} endDate - end date to pass to archive query
* @param {String} title - title to search through archive for
* @param {String} url - URL to pass to archive query
* @returns Array of archived media objects prepped for network transmission.
*/
async prepArchive(startDate, endDate = new Date(), title, url){
//Define array to hold prepped archive objects //Define array to hold prepped archive objects
const preppedArchive = []; const preppedArchive = [];
//Get archive since yesterday //Get archive since yesterday
const archive = await this.queryArchive(startDate, endDate, url); const archive = await this.queryArchive(startDate, endDate);
//Iterate through the channel archive backwards to save time //Iterate through the channel archive backwards to save time
for(let mediaIndex = archive.length - 1; mediaIndex >= 0; mediaIndex--){ for(let mediaIndex = archive.length - 1; mediaIndex >= 0; mediaIndex--){
@ -1980,11 +1921,8 @@ class queue{
media.earlyEnd = null; media.earlyEnd = null;
} }
//If requested title is unspecified or was found in the in the media's title //Add it to the temporary preppd archive array as if it where part of the actual schedule map
if((title == null || title == '') || media.title.includes(title)){ preppedArchive.unshift([media.startTime, media]);
//Add it to the temporary preppd archive array as if it where part of the actual schedule map
preppedArchive.unshift([media.startTime, media]);
}
} }
return preppedArchive; return preppedArchive;
@ -2124,28 +2062,21 @@ class queue{
* Get Channel Archive * Get Channel Archive
* @param {Date} startDate - Start of range to grab from * @param {Date} startDate - Start of range to grab from
* @param {Date} endDate - End of range to grab from * @param {Date} endDate - End of range to grab from
* @param {String} url - URL to search for
*/ */
async queryArchive(startDate, endDate = new Date(), url){ async queryArchive(startDate, endDate = new Date()){
//Define object to contain our DB query
const dbQuery = {
channel: this.channel._id
};
//If we provided a start date //If we provided a start date
if(startDate != null){ if(startDate != null){
//Specify start/end datetime, this one needs to be unsantized for it to werk, I figure its taking in numbers returned by a server-side function, this is fine //Return archived media after start date
dbQuery.startTime = mongoose.trusted({$gt: startDate.getTime(), $lt: endDate.getTime()}); return await archivedMediaModel.find({
channel:this.channel._id,
//Prevent mongoose from attempting to sanatize and invalidate our query object, plus I think we can trust an epoch that came from server-side :)
startTime: mongoose.trusted({$gt: startDate.getTime(), $lt: endDate.getTime()})
});
//Otherwise
}else{
//return all archived media
return await archivedMediaModel.find({channel:this.channel._id});
} }
//Do I really need to re-type this shit for the url?
if(url != null && url != ''){
//Search for exact URL matches
dbQuery.url = {$eq: url};
}
//return archived media based on resulting query
return await archivedMediaModel.find(dbQuery);
} }
} }

View file

@ -50,14 +50,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
<button id="queue-live-prompts-pushback-button" data-mode="pushback" title="Go Live, pushing-back any media scheduled during, or after the live steam." class="danger-button">Go Live (Pushback)</button> <button id="queue-live-prompts-pushback-button" data-mode="pushback" title="Go Live, pushing-back any media scheduled during, or after the live steam." class="danger-button">Go Live (Pushback)</button>
</div> </div>
</div> </div>
<div id="queue-search-prompts" style="display: none;">
<div class="panel-control-prompt control-prompt">
<%# Probably not the cleanest way to do this but fuggit %>
<input placeholder="Search by media title or URL" id="queue-search-prompts-search-input" class="control-prompt">
<button id="queue-search-prompts-search-button" class="positive-button bi-search"></button>
</div>
<div id="queue-search-prompts-result-div"></div>
</div>
<div id="queue-playlist-prompts" style="display: none;"> <div id="queue-playlist-prompts" style="display: none;">
<span class="queue-playlist-label-span interactive" id="queue-add-playlist-span"> <span class="queue-playlist-label-span interactive" id="queue-add-playlist-span">
<i class="bi-plus" id="queue-add-playlist-icon"></i> <i class="bi-plus" id="queue-add-playlist-icon"></i>

View file

@ -144,26 +144,6 @@ div.dragging-queue-entry{
margin: 0.15em 0.3em; margin: 0.15em 0.3em;
} }
/* Search Results */
.queue-search-prompts-result-entry-div{
display: flex;
flex-direction: row;
justify-content: space-between;
text-wrap: nowrap;
margin: 0 0.15em;
padding: 0 0.15em;
font-size: 0.8em
}
.queue-search-prompts-result-entry-div p{
margin: 0;
user-select: none;
}
.queue-search-prompts-result-entry-control-div i{
margin: 0 0.15em;
}
/* playlist manager */ /* playlist manager */
.queue-playlist-label-span{ .queue-playlist-label-span{
display: flex; display: flex;

View file

@ -708,9 +708,8 @@ div.archived p{
} }
.queue-playlist-media-container-div, #queue-search-prompts-result-div:not(:empty){ .queue-playlist-media-container-div{
background-color: var(--bg1-alt0); background-color: var(--bg1-alt0);
color: var(--accent1);
border-block: var(--accent1) solid 1px; border-block: var(--accent1) solid 1px;
} }
@ -727,7 +726,7 @@ div.archived p{
color: var(--accent1); color: var(--accent1);
} }
.queue-playlist-media-div.not-first, .queue-search-prompts-result-entry-div.not-first{ .queue-playlist-media-div.not-first{
border-top: var(--bg1) solid 1px; border-top: var(--bg1) solid 1px;
} }

View file

@ -907,9 +907,8 @@ div.archived p{
} }
.queue-playlist-media-container-div, #queue-search-prompts-result-div:not(:empty){ .queue-playlist-media-container-div{
background-color: var(--bg1-alt0); background-color: var(--bg1-alt0);
color: var(--accent1);
border-block: var(--accent1) solid 1px; border-block: var(--accent1) solid 1px;
} }
@ -926,7 +925,7 @@ div.archived p{
color: var(--accent1); color: var(--accent1);
} }
.queue-playlist-media-div.not-first, .queue-search-prompts-result-entry-div.not-first{ .queue-playlist-media-div.not-first{
border-top: var(--bg1) solid 1px; border-top: var(--bg1) solid 1px;
} }

View file

@ -194,30 +194,6 @@ class channel{
} }
} }
/**
* searches through local un-archived queue for matching titles/urls
* @param {String} title - Title of media to search for
* @param {String} url - URL of media to search fo4
*/
searchQueue(title, url){
//Create array to hold results of the search
const results = [];
//Run through the local queue
for(const rawEntry of this.queue.entries()){
//Quick syntatic sugar XP
const entry = rawEntry[1];
//If the entry title includes our title search string, or the URL is an exact match
if(((title != null && entry.title.includes(title)) || (url != null && entry.url == url)) && entry.status != "archived"){
//Plop that fucker down
results.push(entry);
}
}
//Return all found entries
return results;
}
/** /**
* Processes and applies default config on any unset settings * Processes and applies default config on any unset settings
* @param {Boolean} force - Whether or not to forcefully reset already set settings * @param {Boolean} force - Whether or not to forcefully reset already set settings

View file

@ -88,7 +88,6 @@ 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.goLiveButton = this.panelDocument.querySelector('#queue-go-live'); this.goLiveButton = this.panelDocument.querySelector('#queue-go-live');
this.searchButton = this.panelDocument.querySelector('#queue-search-media');
this.scrollLockButton = this.panelDocument.querySelector('#queue-scroll-lock'); this.scrollLockButton = this.panelDocument.querySelector('#queue-scroll-lock');
this.queueDateButton = this.panelDocument.querySelector('#queue-date'); this.queueDateButton = this.panelDocument.querySelector('#queue-date');
this.playlistMenuButton = this.panelDocument.querySelector("#queue-playlists"); this.playlistMenuButton = this.panelDocument.querySelector("#queue-playlists");
@ -98,7 +97,6 @@ class queuePanel extends panelObj{
//Get control divs //Get control divs
this.addMediaDiv = this.panelDocument.querySelector('#queue-media-prompts'); this.addMediaDiv = this.panelDocument.querySelector('#queue-media-prompts');
this.goLiveDiv = this.panelDocument.querySelector('#queue-live-prompts'); this.goLiveDiv = this.panelDocument.querySelector('#queue-live-prompts');
this.searchDiv = this.panelDocument.querySelector('#queue-search-prompts');
this.queueDateDiv = this.panelDocument.querySelector('#queue-control-date'); this.queueDateDiv = this.panelDocument.querySelector('#queue-control-date');
this.playlistDiv = this.panelDocument.querySelector('#queue-playlist-prompts'); this.playlistDiv = this.panelDocument.querySelector('#queue-playlist-prompts');
@ -112,10 +110,6 @@ class queuePanel extends panelObj{
this.goLiveNamePrompt = this.panelDocument.querySelector("#queue-live-prompts-name-input"); this.goLiveNamePrompt = this.panelDocument.querySelector("#queue-live-prompts-name-input");
this.goLiveOverwriteButton = this.panelDocument.querySelector("#queue-live-prompts-overwrite-button"); this.goLiveOverwriteButton = this.panelDocument.querySelector("#queue-live-prompts-overwrite-button");
this.goLivePushbackButton = this.panelDocument.querySelector("#queue-live-prompts-pushback-button"); this.goLivePushbackButton = this.panelDocument.querySelector("#queue-live-prompts-pushback-button");
//Search
this.searchPrompt = this.panelDocument.querySelector('#queue-search-prompts-search-input');
this.searchPromptButton = this.panelDocument.querySelector('#queue-search-prompts-search-button');
this.searchResultDiv = this.panelDocument.querySelector('#queue-search-prompts-result-div')
//Date Queue date //Date Queue date
this.queueDateDecrement = this.panelDocument.querySelector('#queue-control-date-decrement'); this.queueDateDecrement = this.panelDocument.querySelector('#queue-control-date-decrement');
this.queueDateIncrement = this.panelDocument.querySelector('#queue-control-date-increment'); this.queueDateIncrement = this.panelDocument.querySelector('#queue-control-date-increment');
@ -169,7 +163,6 @@ 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.goLiveButton.addEventListener('click', this.toggleGoLive.bind(this)); this.goLiveButton.addEventListener('click', this.toggleGoLive.bind(this));
this.searchButton.addEventListener('click', this.toggleSearch.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)); this.queueDateButton.addEventListener('click', this.toggleDateControl.bind(this));
this.playlistMenuButton.addEventListener('click', this.togglePlaylistDiv.bind(this)); this.playlistMenuButton.addEventListener('click', this.togglePlaylistDiv.bind(this));
@ -281,90 +274,6 @@ class queuePanel extends panelObj{
} }
} }
/**
* Toggles add media UX
* @param {Event} event - Event passed down from Event Listener
*/
toggleSearch(event){
//If the div is hidden
if(this.searchDiv.style.display == 'none'){
//Light up the button
this.searchButton.classList.add('positive-button');
//Show the div
this.searchDiv.style.display = '';
}else{
//Unlight the button
this.searchButton.classList.remove('positive-button');
//Hide the div
this.searchDiv.style.display = 'none';
}
}
search(event){
//Grab query
const query = this.searchPrompt.value;
//If we're searching by URL
if(utils.isURL(query)){
//Render local items matching provided URL
this.renderResults(client.searchQueue(undefined, query));
}else{
//Render local items matching title
this.renderResults(client.searchQueue(query));
}
}
renderResults(results){
//Keep track of whether or not we're on the first entry
let notFirst = false;
for(const entry of results){
//Create entry div
const entryDiv = document.createElement('div');
entryDiv.classList.add('queue-search-prompts-result-entry-div');
if(notFirst){
entryDiv.classList.add('not-first');
}
//Create and append entryTitle
const entryTitle = document.createElement('p');
entryTitle.textContent = utils.unescapeEntities(entry.title);
//Setup media tooltip
entryTitle.addEventListener('mouseenter',(event)=>{
//Display tooltip
utils.ux.displayTooltip(event, this.renderMediaTooltip(entry), false, null, true, this.ownerDoc);
});
//Append entry title
entryDiv.appendChild(entryTitle);
//Create a div to hold result controls
const controlDiv = document.createElement('div');
controlDiv.classList.add('queue-search-prompts-result-entry-control-div');
//Create go to entry button
const goToEntry = document.createElement('i');
goToEntry.classList.add('bi-arrow-return-left')
controlDiv.appendChild(goToEntry);
//Create queue entry button
const queueEntry = document.createElement('i');
queueEntry.classList.add('bi-play-circle')
controlDiv.appendChild(queueEntry);
//Append control div to entry div
entryDiv.appendChild(controlDiv);
//Add result to the result container
this.searchResultDiv.appendChild(entryDiv);
//Throw not first flag for future entries
notFirst = true;
}
}
/** /**
* Toggles Go-Live UX * Toggles Go-Live UX
* @param {Event} event - Event passed down from Event Listener * @param {Event} event - Event passed down from Event Listener
@ -857,7 +766,26 @@ class queuePanel extends panelObj{
//Tooltip generation //Tooltip generation
//tooltip div //tooltip div
const tooltipDiv = this.renderMediaTooltip(entry[1]); const tooltipDiv = document.createElement('div');
tooltipDiv.classList.add('media-tooltip');
//tooltip components
//For each string
for(let string of [
`Title: ${entry[1].title}`,
`File Name: ${entry[1].fileName}`,
`Source: ${entry[1].type}`,
`Duration: ${utils.ux.humieFriendlyDuration(entry[1].duration)}`,
`Start Time: ${new Date(entry[1].startTime).toLocaleString()}${entry[1].startTimeStamp == 0 ? '' : ' (Started Late)'}`,
`End Time: ${new Date(this.getMediaEnd(entry[1])).toLocaleString()}${entry[1].earlyEnd == null ? '' : ' (Ended Early)'}`
]){
//Create a 'p' node
const component = document.createElement('p');
//Fill it with the string
component.textContent = utils.unescapeEntities(string);
//Append it to the tooltip div
tooltipDiv.append(component);
}
//Setup media tooltip //Setup media tooltip
entryDiv.addEventListener('mouseenter',(event)=>{ entryDiv.addEventListener('mouseenter',(event)=>{
@ -1114,38 +1042,6 @@ class queuePanel extends panelObj{
} }
/**
* Ingests a media object and returns a working tooltip ready to be placed into DOM
* @param {Object} media - Media object to render tooltip for
* @returns DOMNode containing tooltip div
*/
renderMediaTooltip(media){
//Create tooltip div
const tooltipDiv = document.createElement('div');
tooltipDiv.classList.add('media-tooltip');
//tooltip components
//For each string
for(let string of [
`Title: ${media.title}`,
`File Name: ${media.fileName}`,
`Source: ${media.type}`,
`Duration: ${utils.ux.humieFriendlyDuration(media.duration)}`,
`Start Time: ${new Date(media.startTime).toLocaleString()}${media.startTimeStamp == 0 ? '' : ' (Started Late)'}`,
`End Time: ${new Date(this.getMediaEnd(media)).toLocaleString()}${media.earlyEnd == null ? '' : ' (Ended Early)'}`
]){
//Create a 'p' node
const component = document.createElement('p');
//Fill it with the string
component.textContent = utils.unescapeEntities(string);
//Append it to the tooltip div
tooltipDiv.append(component);
}
//Return tooltip
return tooltipDiv;
}
/** /**
* Kills off hung tooltips * Kills off hung tooltips
* @param {Event} event - Event passed down from Event Listener * @param {Event} event - Event passed down from Event Listener

View file

@ -44,20 +44,6 @@ class canopyUtils{
return min.getTime() < input.getTime() && max.getTime() > input.getTime(); return min.getTime() < input.getTime() && max.getTime() > input.getTime();
} }
isURL(url){
//attempt to makem a url
try{
new URL(url);
//if it dont work
}catch{
//FUCK
return false;
}
//if it do, return true XP
return true;
}
} }
class canopyUXUtils{ class canopyUXUtils{