Compare commits
5 commits
d0ef60dd4f
...
3ec770ba28
| Author | SHA1 | Date | |
|---|---|---|---|
| 3ec770ba28 | |||
| c081d9c4ff | |||
| 2fe9b71271 | |||
| ccb468078c | |||
| 42accf52ca |
10 changed files with 326 additions and 77 deletions
|
|
@ -106,7 +106,8 @@ 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.",
|
||||||
|
|
@ -134,7 +135,6 @@ class activeChannel{
|
||||||
"That'll do, %UNAME%."
|
"That'll do, %UNAME%."
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -150,6 +150,8 @@ 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){
|
||||||
|
|
@ -161,6 +163,8 @@ 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
|
||||||
|
|
@ -175,7 +179,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)
|
const activeUser = await userObj.handleConnection(userDB, chanDB, socket, first)
|
||||||
|
|
||||||
//Send out the userlist
|
//Send out the userlist
|
||||||
this.broadcastUserList(socket.chan);
|
this.broadcastUserList(socket.chan);
|
||||||
|
|
@ -267,7 +271,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, []);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -96,11 +96,12 @@ 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){
|
async handleConnection(userDB, chanDB, socket, first){
|
||||||
//send metadata to client
|
//send metadata to client
|
||||||
this.sendClientMetadata();
|
this.sendClientMetadata(undefined, undefined, first);
|
||||||
|
|
||||||
//Send out emotes
|
//Send out emotes
|
||||||
this.sendSiteEmotes();
|
this.sendSiteEmotes();
|
||||||
|
|
@ -122,7 +123,6 @@ 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,8 +179,9 @@ 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){
|
async sendClientMetadata(userDB, chanDB, welcome){
|
||||||
//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 = [];
|
||||||
|
|
@ -238,8 +239,11 @@ 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});
|
||||||
|
|
||||||
//Broadcast the welcome wagon
|
//If we where told to welcome the user
|
||||||
this.channel.welcomeWagon(userDB.user);
|
if(welcome){
|
||||||
|
//Broadcast the welcome wagon
|
||||||
|
this.channel.welcomeWagon(userDB.user);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -518,29 +518,6 @@ 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});
|
||||||
|
|
@ -551,12 +528,86 @@ class queue{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Create new date objects to represent range of data requested
|
let startDate = undefined;
|
||||||
const startDate = new Date(data.startDate);
|
let endDate = undefined;
|
||||||
const endDate = new Date(data.endDate);
|
let title = undefined;
|
||||||
|
let url = undefined
|
||||||
|
|
||||||
//Grab archives from given range, and send them back to the requesting socket
|
//If we where supplied with a start date
|
||||||
socket.emit("archive", {archive: await this.prepArchive(startDate, endDate)});
|
if(data.startDate != null){
|
||||||
|
//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);
|
||||||
}
|
}
|
||||||
|
|
@ -1903,12 +1954,20 @@ 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);
|
const archive = await this.queryArchive(startDate, endDate, url);
|
||||||
|
|
||||||
//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--){
|
||||||
|
|
@ -1921,8 +1980,11 @@ class queue{
|
||||||
media.earlyEnd = null;
|
media.earlyEnd = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Add it to the temporary preppd archive array as if it where part of the actual schedule map
|
//If requested title is unspecified or was found in the in the media's title
|
||||||
preppedArchive.unshift([media.startTime, media]);
|
if((title == null || title == '') || media.title.includes(title)){
|
||||||
|
//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;
|
||||||
|
|
@ -2062,21 +2124,28 @@ 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()){
|
async queryArchive(startDate, endDate = new Date(), url){
|
||||||
|
//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){
|
||||||
//Return archived media after start date
|
//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 await archivedMediaModel.find({
|
dbQuery.startTime = mongoose.trusted({$gt: startDate.getTime(), $lt: endDate.getTime()});
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,14 @@ 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>
|
||||||
|
|
|
||||||
|
|
@ -144,6 +144,26 @@ 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;
|
||||||
|
|
|
||||||
|
|
@ -708,8 +708,9 @@ div.archived p{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.queue-playlist-media-container-div{
|
.queue-playlist-media-container-div, #queue-search-prompts-result-div:not(:empty){
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -726,7 +727,7 @@ div.archived p{
|
||||||
color: var(--accent1);
|
color: var(--accent1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.queue-playlist-media-div.not-first{
|
.queue-playlist-media-div.not-first, .queue-search-prompts-result-entry-div.not-first{
|
||||||
border-top: var(--bg1) solid 1px;
|
border-top: var(--bg1) solid 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -907,8 +907,9 @@ div.archived p{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.queue-playlist-media-container-div{
|
.queue-playlist-media-container-div, #queue-search-prompts-result-div:not(:empty){
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -925,7 +926,7 @@ div.archived p{
|
||||||
color: var(--accent1);
|
color: var(--accent1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.queue-playlist-media-div.not-first{
|
.queue-playlist-media-div.not-first, .queue-search-prompts-result-entry-div.not-first{
|
||||||
border-top: var(--bg1) solid 1px;
|
border-top: var(--bg1) solid 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -194,6 +194,30 @@ 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
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,7 @@ 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");
|
||||||
|
|
@ -97,6 +98,7 @@ 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');
|
||||||
|
|
||||||
|
|
@ -110,6 +112,10 @@ 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');
|
||||||
|
|
@ -163,6 +169,7 @@ 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));
|
||||||
|
|
@ -274,6 +281,90 @@ 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
|
||||||
|
|
@ -766,26 +857,7 @@ class queuePanel extends panelObj{
|
||||||
|
|
||||||
//Tooltip generation
|
//Tooltip generation
|
||||||
//tooltip div
|
//tooltip div
|
||||||
const tooltipDiv = document.createElement('div');
|
const tooltipDiv = this.renderMediaTooltip(entry[1]);
|
||||||
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)=>{
|
||||||
|
|
@ -1042,6 +1114,38 @@ 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
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,20 @@ 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{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue