Started work on playlist management UI

This commit is contained in:
rainbow napkin 2025-03-30 14:11:49 -04:00
parent 65b5ae9371
commit 1723e8ebd0
9 changed files with 586 additions and 104 deletions

View file

@ -32,106 +32,196 @@ module.exports = class{
} }
defineListeners(socket){ defineListeners(socket){
//socket.on("queue", (data) => {this.queueURL(socket, data)});
socket.on("getChannelPlaylists", () => {this.getChannelPlaylists(socket)}); socket.on("getChannelPlaylists", () => {this.getChannelPlaylists(socket)});
socket.on("createChannelPlaylist", (data) => {this.createChannelPlaylist(socket, data)}); socket.on("createChannelPlaylist", (data) => {this.createChannelPlaylist(socket, data)});
socket.on("deleteChannelPlaylist", (data) => {this.deleteChannelPlaylist(socket, data)}); socket.on("deleteChannelPlaylist", (data) => {this.deleteChannelPlaylist(socket, data)});
socket.on("addToChannelPlaylist", (data) => {this.addToChannelPlaylist(socket, data)}); socket.on("addToChannelPlaylist", (data) => {this.addToChannelPlaylist(socket, data)});
socket.on("queueChannelPlaylist", (data) => {this.queueChannelPlaylist(socket, data)}); socket.on("queueChannelPlaylist", (data) => {this.queueChannelPlaylist(socket, data)});
socket.on("renameChannelPlaylist", (data) => {this.renameChannelPlaylist(socket, data)});
} }
//--- USER-FACING PLAYLIST FUNCTIONS --- //--- USER-FACING PLAYLIST FUNCTIONS ---
async getChannelPlaylists(socket, chanDB){ async getChannelPlaylists(socket, chanDB){
//if we wherent handed a channel document try{
if(chanDB == null){ //if we wherent handed a channel document
//Pull it based on channel name if(chanDB == null){
chanDB = await channelModel.findOne({name: this.channel.name}); //Pull it based on channel name
} chanDB = await channelModel.findOne({name: this.channel.name});
}
//Return playlists //Return playlists
socket.emit('chanPlaylists', chanDB.getPlaylists()); socket.emit('chanPlaylists', chanDB.getPlaylists());
}catch(err){
return loggerUtils.socketExceptionHandler(socket, err);
}
} }
async createChannelPlaylist(socket, data, chanDB){ async createChannelPlaylist(socket, data, chanDB){
//if we wherent handed a channel document try{
if(chanDB == null){ //if we wherent handed a channel document
//Pull it based on channel name if(chanDB == null){
chanDB = await channelModel.findOne({name: this.channel.name}); //Pull it based on channel name
chanDB = await channelModel.findOne({name: this.channel.name});
}
//If the title is too long
if(!validator.isLength(data.playlist, {max:30})){
//Bitch, moan, complain...
loggerUtils.socketErrorHandler(socket, "Playlist name too long!", "validation");
//and ignore it!
return;
}
//Escape/trim the playlist name
const name = validator.escape(validator.trim(data.playlist));
//Add playlist to the channel doc
chanDB.media.playlists.push({
name,
defaultTitles: data.defaultTitles
});
//Save the channel doc
await chanDB.save();
//Return playlists from channel doc
socket.emit('chanPlaylists', chanDB.getPlaylists());
}catch(err){
return loggerUtils.socketExceptionHandler(socket, err);
} }
//Add playlist to the channel doc
chanDB.media.playlists.push({
name: data.playlist,
defaultTitles: data.defaultTitles
});
//Save the channel doc
await chanDB.save();
//Return playlists from channel doc
socket.emit('chanPlaylists', chanDB.getPlaylists());
} }
async deleteChannelPlaylist(socket, data, chanDB){ async deleteChannelPlaylist(socket, data, chanDB){
//if we wherent handed a channel document try{
if(chanDB == null){ //if we wherent handed a channel document
//Pull it based on channel name if(chanDB == null){
chanDB = await channelModel.findOne({name: this.channel.name}); //Pull it based on channel name
chanDB = await channelModel.findOne({name: this.channel.name});
}
//Delete playlist name
await chanDB.deletePlaylistByName(data.playlist);
//Return playlists from channel doc
socket.emit('chanPlaylists', chanDB.getPlaylists());
}catch(err){
return loggerUtils.socketExceptionHandler(socket, err);
} }
//Delete playlist name
await chanDB.deletePlaylistByName(data.playlist);
//Return playlists from channel doc
socket.emit('chanPlaylists', chanDB.getPlaylists());
} }
async addToChannelPlaylist(socket, data, chanDB){ async addToChannelPlaylist(socket, data, chanDB){
//if we wherent handed a channel document try{
if(chanDB == null){ //if we wherent handed a channel document
//Pull it based on channel name if(chanDB == null){
chanDB = await channelModel.findOne({name: this.channel.name}); //Pull it based on channel name
chanDB = await channelModel.findOne({name: this.channel.name});
}
let url = data.url
//If we where given a bad URL
if(!validator.isURL(url)){
//Attempt to fix the situation by encoding it
url = encodeURI(url);
//If it's still bad
if(!validator.isURL(url)){
//Bitch, moan, complain...
loggerUtils.socketErrorHandler(socket, "Bad URL!", "validation");
//and ignore it!
return;
}
}
//Pull media metadata
let mediaList = await yanker.yankMedia(url);
//If we didn't get any media
if(mediaList.length == 0 || mediaList == null){
//Bitch, moan, complain...
loggerUtils.socketErrorHandler(socket, "No media found!", "queue");
//and ignore it!
return;
}
//Add media object to the given playlist
await chanDB.addToPlaylist(data.playlist, mediaList[0]);
//Return playlists from channel doc
socket.emit('chanPlaylists', chanDB.getPlaylists());
}catch(err){
return loggerUtils.socketExceptionHandler(socket, err);
} }
//Pull media metadata
let mediaList = await yanker.yankMedia(data.url);
//Add media object to the given playlist
await chanDB.addToPlaylist(data.playlist, mediaList[0]);
//Return playlists from channel doc
socket.emit('chanPlaylists', chanDB.getPlaylists());
} }
async queueChannelPlaylist(socket, data, chanDB){ async queueChannelPlaylist(socket, data, chanDB){
//if we wherent handed a channel document try{
if(chanDB == null){ //if we wherent handed a channel document
//Pull it based on channel name if(chanDB == null){
chanDB = await channelModel.findOne({name: this.channel.name}); //Pull it based on channel name
chanDB = await channelModel.findOne({name: this.channel.name});
}
//Pull a valid start time from input, or make one up if we can't
let start = this.channel.queue.getStart(data.start);
//Grab playlist from the DB
let playlist = chanDB.getPlaylistByName(data.playlist);
//Create an empty array to hold our media list
const mediaList = [];
//Iterate through playlist media
for(let item of playlist.media){
//Rehydrate a full phat media object from the flat DB entry
let mediaObj = item.rehydrate();
//Set media title from default titles
mediaObj.title = playlist.defaultTitles[Math.floor(Math.random() * playlist.defaultTitles.length)];
//Push rehydrated item on to the mediaList
mediaList.push(mediaObj);
}
//Convert array of standard media objects to queued media objects, and push to schedule
this.channel.queue.scheduleMedia(queuedMedia.fromMediaArray(mediaList, start), socket, chanDB);
}catch(err){
return loggerUtils.socketExceptionHandler(socket, err);
} }
}
//Pull a valid start time from input, or make one up if we can't async renameChannelPlaylist(socket, data, chanDB){
let start = this.channel.queue.getStart(data.start); try{
//if we wherent handed a channel document
if(chanDB == null){
//Pull it based on channel name
chanDB = await channelModel.findOne({name: this.channel.name});
}
//Grab playlist from the DB //If the title is too long
let playlist = chanDB.getPlaylistByName(data.playlist); if(!validator.isLength(data.name, {max:30})){
//Bitch, moan, complain...
loggerUtils.socketErrorHandler(socket, "Playlist name too long!", "validation");
//and ignore it!
return;
}
//Create an empty array to hold our media list //Escape/trim the playlist name
const mediaList = []; const name = validator.escape(validator.trim(data.name));
//Iterate through playlist media //Find playlist
for(let item of playlist.media){ let playlist = chanDB.getPlaylistByName(data.playlist);
//Rehydrate a full phat media object from the flat DB entry
let mediaObj = item.rehydrate();
//Set media title from default titles //Change playlist name
mediaObj.title = playlist.defaultTitles[Math.floor(Math.random() * playlist.defaultTitles.length)]; chanDB.media.playlists[playlist.listIndex].name = name;
//Push rehydrated item on to the mediaList //Save channel document
mediaList.push(mediaObj); await chanDB.save();
//Return playlists from channel doc
socket.emit('chanPlaylists', chanDB.getPlaylists());
}catch(err){
return loggerUtils.socketExceptionHandler(socket, err);
} }
//Convert array of standard media objects to queued media objects, and push to schedule
this.channel.queue.scheduleMedia(queuedMedia.fromMediaArray(mediaList, start), socket, chanDB);
} }
} }

View file

@ -626,7 +626,7 @@ module.exports = class{
} }
//If we fucked with the DB during the main loop //If we fucked with the DB during the main loop
if(chanDB == null && !volatile){ if(chanDB != null && !volatile){
try{ try{
//Save the database //Save the database
chanDB.save(); chanDB.save();

View file

@ -559,10 +559,7 @@ channelSchema.methods.getPlaylists = function(){
//this.media.playlists.forEach((playlist) => { //this.media.playlists.forEach((playlist) => {
for(let playlist of this.media.playlists){ for(let playlist of this.media.playlists){
//Push an object with select information from the emote to the emote list //Push an object with select information from the emote to the emote list
playlists.push({ playlists.push(playlist.dehydrate());
name: playlist.name,
count: playlist.media.length
});
} }
//return the emote list //return the emote list
@ -584,11 +581,13 @@ channelSchema.methods.getPlaylistByName = function(name){
let foundPlaylist = null; let foundPlaylist = null;
//Crawl through active playlists //Crawl through active playlists
this.playlistCrawl((playlist) => { this.playlistCrawl((playlist, listIndex) => {
//If we found a match based on name //If we found a match based on name
if(playlist.name == name){ if(playlist.name == name){
//Keep it //Keep it
foundPlaylist = playlist; foundPlaylist = playlist;
//Pass down the list index
foundPlaylist.listIndex = listIndex;
} }
}); });
@ -597,45 +596,25 @@ channelSchema.methods.getPlaylistByName = function(name){
} }
channelSchema.methods.deletePlaylistByName = async function(name){ channelSchema.methods.deletePlaylistByName = async function(name){
//Create null value to hold our found playlist //Find the playlist
let foundIndex = null; let playlist = this.getPlaylistByName(name);
//Crawl through active playlists
this.playlistCrawl((playlist, listIndex) => {
//If we found a match based on name
if(playlist.name == name){
//Yoink it's index
foundIndex = listIndex;
}
});
//splice out the given playlist //splice out the given playlist
this.media.playlists.splice(foundIndex, 1); this.media.playlists.splice(playlist.listIndex, 1);
//save the channel document //save the channel document
await this.save(); await this.save();
} }
channelSchema.methods.addToPlaylist = async function(name, media){ channelSchema.methods.addToPlaylist = async function(name, media){
//Create variable to hold found index //Find the playlist
let foundIndex = null let playlist = this.getPlaylistByName(name);
//Crawl through active playlists
this.playlistCrawl((playlist, listIndex) => {
//If the playlist name matches
if(playlist.name == name){
//Push the given media into the found playlist
//Make note of the found index
foundIndex = listIndex
}
});
//Set media status schema discriminator //Set media status schema discriminator
media.status = 'saved'; media.status = 'saved';
//Add the media to the playlist //Add the media to the playlist
this.media.playlists[foundIndex].media.push(media); this.media.playlists[playlist.listIndex].media.push(media);
//Save the changes made to the chan doc //Save the changes made to the chan doc
await this.save(); await this.save();

View file

@ -24,6 +24,7 @@ const playlistSchema = new mongoose.Schema({
name: { name: {
type: mongoose.SchemaTypes.String, type: mongoose.SchemaTypes.String,
required: true, required: true,
unique: true
}, },
media: [playlistMediaSchema], media: [playlistMediaSchema],
defaultTitles:[{ defaultTitles:[{
@ -33,8 +34,26 @@ const playlistSchema = new mongoose.Schema({
}] }]
}); });
playlistSchema.methods.test = function(){ //methods
console.log(this.name); playlistSchema.methods.dehydrate = function(){
//Create empty array to hold media
const mediaArray = [];
//Fill media array
for(let media of this.media){
mediaArray.push({
title: media.title,
url: media.url,
duration: media.duration
});
}
//return dehydrated playlist
return {
name: this.name,
media: mediaArray,
defaultTitles: this.defaultTitles
}
} }
module.exports = playlistSchema; module.exports = playlistSchema;

View file

@ -41,6 +41,22 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
<button id="queue-at-button" class="positive-button">Queue At...</button> <button id="queue-at-button" class="positive-button">Queue At...</button>
</div> </div>
</div> </div>
<div id="queue-playlist-prompts" style="display: none;">
<span class="queue-playlist-label-span interactive" id="queue-add-playlist-span">
<i class="bi-plus" id="queue-add-playlist-icon"></i>
<p id="queue-add-playlist-label">Create Playlist</p>
</span>
<span class="queue-playlist-label-span interactive" id="queue-channel-playlist-span">
<i class="bi-caret-right-fill" id="queue-channel-playlist-toggle"></i>
<p id="queue-channel-playlist-label">Channel Playlists</p>
</span>
<div style="display: none;" id="queue-channel-playlist-div" class="queue-playlists-div"></div>
<span class="queue-playlist-label-span interactive" id="queue-user-playlist-span">
<i class="bi-caret-right-fill" id="queue-user-playlist-toggle"></i>
<p id="queue-user-playlist-label">User Playlists</p>
</span>
<div style="display: none;" id="queue-user-playlist-div" class="queue-playlists-div"></div>
</div>
</div> </div>
<div id="queue-control-offset"></div> <div id="queue-control-offset"></div>
<div id="queue"> <div id="queue">

View file

@ -0,0 +1,28 @@
<%# Canopy - The next generation of stoner streaming software
Copyright (C) 2024-2025 Rainbownapkin and the TTN Community
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. %>
<h3 class="popup-title">Create Playlist</h3>
<div id="queue-create-playlist-popup-div">
<input id="queue-create-playlist-popup-name" placeholder="Name">
<textarea id="queue-create-playlist-popup-default-titles" placeholder="Default Titles"></textarea>
<span>
<label for="location">Save To:</label>
<select name="location" id="queue-create-playlist-popup-location">
<option value="channel">Channel</option>
<option value="user">Account</option>
</select>
</span>
<button id="queue-create-playlist-popup-save" class="positive-button">Create Playlist</button>
</div>

View file

@ -25,7 +25,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
left: 0; left: 0;
right: 0; right: 0;
z-index: 4; z-index: 4;
padding: 0.15em 0.3em;
} }
#queue-control-offset{ #queue-control-offset{
@ -38,6 +37,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
margin: 0.15em 0.3em;
} }
@ -133,6 +133,82 @@ div.dragging-queue-entry{
} }
/* queue controls */ /* queue controls */
/* media prompts */
#queue-media-prompts{
margin: 0.15em 0.3em;
}
/* playlist manager */
.queue-playlist-label-span{
display: flex;
flex-direction: row;
width: fit-content;
margin-left: 0.15em;
}
.queue-playlist-label-span p{
margin: 0;
}
.queue-playlists-div{
margin: 0;
}
.queue-playlist-span, .queue-playlist-labels-span{
display: flex;
flex-direction: row;
align-items: baseline;
text-wrap: nowrap;
}
.queue-playlist-span{
justify-content: space-between;
}
.queue-playlist-div{
padding: 0 0.15em;
margin: 0 0.15em;
}
.queue-playlist-control{
border-radius: 0;
padding: 0.2em 0.1em;
font-size: 0.8em;
}
.queue-playlist-span p{
margin: 0 0.15em;
user-select: none;
}
.queue-playlist-count{
font-size: 0.8em;
}
#queue-create-playlist-popup-div{
display: flex;
flex-direction: column;
}
#queue-create-playlist-popup-div span{
display: flex;
justify-content: space-around;
text-wrap: nowrap;
}
#queue-create-playlist-popup-div span select{
width: 100%;
}
#queue-create-playlist-popup-div *{
margin: 0.1em;
}
#queue-create-playlist-popup-default-titles{
resize: vertical;
}
/* date */ /* date */
#queue-control-date{ #queue-control-date{
display: flex; display: flex;

View file

@ -539,6 +539,25 @@ div.archived p{
color: var(--bg2-alt1); color: var(--bg2-alt1);
} }
.queue-playlists-div{
background-color: var(--bg1);
color: var(--accent1);
border-block: var(--accent1) solid 1px;
}
.not-first-queue-playlist-div{
border-top: var(--bg1-alt0) solid 1px;
}
.queue-playlist-count{
color: var(--accent1-alt0);
}
.queue-playlist-control:not(.danger-button):not(:hover){
background-color: var(--bg1-alt0);
color: 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

@ -17,6 +17,9 @@ class queuePanel extends panelObj{
//Autoscroll boolean //Autoscroll boolean
this.autoscroll = true; this.autoscroll = true;
//Setup child classes
this.playlistManager = new playlistManager(client, panelDocument, this);
//Define non-input event listeners //Define non-input event listeners
this.defineListeners(); this.defineListeners();
} }
@ -43,13 +46,15 @@ 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') this.queueDateButton = this.panelDocument.querySelector('#queue-date');
this.playlistMenuButton = this.panelDocument.querySelector("#queue-playlists");
this.clearMediaButton = this.panelDocument.querySelector('#queue-clear'); this.clearMediaButton = this.panelDocument.querySelector('#queue-clear');
this.queueLockButton = this.panelDocument.querySelector('#queue-lock'); this.queueLockButton = this.panelDocument.querySelector('#queue-lock');
//Get control divs //Get control divs
this.addMediaDiv = this.panelDocument.querySelector('#queue-media-prompts'); this.addMediaDiv = this.panelDocument.querySelector('#queue-media-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');
//Get control div elements //Get control div elements
//Add Media //Add Media
@ -70,6 +75,10 @@ class queuePanel extends panelObj{
//Setup panel input //Setup panel input
this.setupInput(); this.setupInput();
//Pass the new panelDoc and docSwitch() call down to child classes
this.playlistManager.panelDocument = this.panelDocument;
this.playlistManager.docSwitch();
} }
closer(){ closer(){
@ -99,6 +108,7 @@ class queuePanel extends panelObj{
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)); this.queueDateButton.addEventListener('click', this.toggleDateControl.bind(this));
this.playlistMenuButton.addEventListener('click', this.togglePlaylistDiv.bind(this));
this.clearMediaButton.addEventListener('click', this.clearMedia.bind(this)); this.clearMediaButton.addEventListener('click', this.clearMedia.bind(this));
this.queueLockButton.addEventListener('click', this.lockSchedule.bind(this)); this.queueLockButton.addEventListener('click', this.lockSchedule.bind(this));
@ -203,6 +213,24 @@ class queuePanel extends panelObj{
} }
} }
togglePlaylistDiv(event){
//If the div is hidden
if(this.playlistDiv.style.display == 'none'){
//Query playlists
client.socket.emit('getChannelPlaylists');
//Light up the button
this.playlistMenuButton.classList.add('positive-button');
//Show the div
this.playlistDiv.style.display = '';
}else{
//Unlight the button
this.playlistMenuButton.classList.remove('positive-button');
//Hide the div
this.playlistDiv.style.display = 'none';
}
}
clearMedia(event){ clearMedia(event){
//Call up the popup //Call up the popup
new clearPopup(event, this.client, null, this.ownerDoc); new clearPopup(event, this.client, null, this.ownerDoc);
@ -1003,6 +1031,233 @@ class queuePanel extends panelObj{
} }
} }
class playlistManager{
constructor(client, panelDocument, queuePanel){
//Set client
this.client = client;
//Set panel document
this.panelDocument = panelDocument;
//Set parent queue panel
this.queuePanel = queuePanel;
//Define Listeners
this.defineListeners();
}
defineListeners(){
this.client.socket.on("chanPlaylists", this.renderChannelPlaylists.bind(this));
}
docSwitch(){
//Grab menus
this.channelPlaylistDiv = this.panelDocument.querySelector("#queue-channel-playlist-div");
//Grab controls
this.createPlaylistSpan = this.panelDocument.querySelector('#queue-add-playlist-span');
this.channelPlaylistLabel = this.panelDocument.querySelector('#queue-channel-playlist-span');
this.channelPlaylistCaret = this.panelDocument.querySelector('#queue-channel-playlist-toggle');
//Setup Input
this.setupInput();
}
setupInput(){
this.createPlaylistSpan.addEventListener('click', (event)=>{new newPlaylistPopup(event, this.client, this.queuePanel.ownerDoc)})
this.channelPlaylistLabel.addEventListener('click', this.toggleChannelPlaylists.bind(this));
}
/* queue control button functions */
toggleChannelPlaylists(event){
//If the div is hidden
if(this.channelPlaylistDiv.style.display == 'none'){
//Light up the button
this.channelPlaylistLabel.classList.add('positive');
//Flip the caret
this.channelPlaylistCaret.classList.replace('bi-caret-right-fill', 'bi-caret-down-fill');
//Show the div
this.channelPlaylistDiv.style.display = '';
}else{
//Unlight the button
this.channelPlaylistLabel.classList.remove('positive');
//Flip the caret
this.channelPlaylistCaret.classList.replace('bi-caret-down-fill', 'bi-caret-right-fill');
//Hide the div
this.channelPlaylistDiv.style.display = 'none';
}
}
renderChannelPlaylists(data){
//Clear channel playlist div
this.channelPlaylistDiv.innerHTML = '';
//For every playlist sent down from the server
for(let playlistIndex in data){
//Get playlist from data
const playlist = data[playlistIndex];
//--Create Base Structure---
//Create a new playlist div
const playlistDiv = document.createElement('div');
//Set it's class
playlistDiv.classList.add('queue-playlist-div');
//Set playlist div dataset
playlistDiv.dataset.name = playlist.name;
//If this isn't our first rodeo
if(playlistIndex != 0){
//make note
playlistDiv.classList.add('not-first-queue-playlist-div');
}
//Create span to hold playlist entry line contents
const playlistSpan = document.createElement('span');
//Set classes
playlistSpan.classList.add('queue-playlist-span');
//--Create Labels---
//Create playlist label span
const playlistLabels = document.createElement('span');
//Set it's class
playlistLabels.classList.add('queue-playlist-labels-span');
//Create playlist title label
const playlistTitle = document.createElement('p');
//Set it's class
playlistTitle.classList.add('queue-playlist-title');
//Create playlist count label
const playlistCount = document.createElement('p');
//Set it's class
playlistCount.classList.add('queue-playlist-count');
//List video count
playlistCount.innerText = `Count: ${playlist.media.length}`;
//--Create Controls--
//Create playlist control span
const playlistControls = document.createElement('span');
//Set it's class
playlistControls.classList.add('queue-playlist-control-span');
//Unescape Sanatized Enteties and safely inject as plaintext
playlistTitle.innerText = utils.unescapeEntities(playlist.name);
//Create queue all button
const playlistQueueRandomButton = document.createElement('button');
//Set it's classes
playlistQueueRandomButton.classList.add('queue-playlist-queue-all-button', 'queue-playlist-control');
//Inject text content
playlistQueueRandomButton.textContent = 'Queue Random';
//Create queue all button
const playlistQueueAllButton = document.createElement('button');
//Set it's classes
playlistQueueAllButton.classList.add('queue-playlist-queue-all-button', 'queue-playlist-control');
//Inject text content
playlistQueueAllButton.textContent = 'Queue All';
//Create delete button
const playlistDeleteButton = document.createElement('button');
//Set it's classes
playlistDeleteButton.classList.add('queue-playlist-delete-button', 'queue-playlist-control', 'danger-button', 'bi-trash-fill');
//--Create Media Elements--
//Create Media Container Div
const mediaDiv = renderMedia();
//Append items to playlist labels span
playlistLabels.appendChild(playlistTitle);
playlistLabels.appendChild(playlistCount);
//Append items to playlist control span
playlistControls.appendChild(playlistQueueRandomButton);
playlistControls.appendChild(playlistQueueAllButton);
playlistControls.appendChild(playlistDeleteButton);
//Append items to playlist span
playlistSpan.appendChild(playlistLabels);
playlistSpan.appendChild(playlistControls);
//Append items to playlist div
playlistDiv.appendChild(playlistSpan);
playlistDiv.appendChild(mediaDiv);
//Append current playlist span to the channel playlist div
this.channelPlaylistDiv.appendChild(playlistDiv);
//Define input event listeners
playlistQueueAllButton.addEventListener('click', queueAll);
playlistDeleteButton.addEventListener('click', deletePlaylist);
//aux rendering functions
function renderMedia(){
//Create media container div
const mediaContainer = document.createElement('div');
//Set classes
mediaContainer.classList.add('queue-playlist-media-div');
//return media container
return mediaContainer;
}
//playlist control functions
function queueAll(){
client.socket.emit('queueChannelPlaylist', {playlist: playlist.name});
}
function deletePlaylist(){
client.socket.emit('deleteChannelPlaylist', {playlist: playlist.name});
}
}
}
}
class newPlaylistPopup{
constructor(event, client, doc){
//Set Client
this.client = client;
//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('/newPlaylist', true, this.asyncConstructor.bind(this), doc);
}
asyncConstructor(){
this.name = this.popup.contentDiv.querySelector('#queue-create-playlist-popup-name');
this.defaultTitles = this.popup.contentDiv.querySelector('#queue-create-playlist-popup-default-titles');
this.location = this.popup.contentDiv.querySelector('#queue-create-playlist-popup-location');
this.saveButton = this.popup.contentDiv.querySelector('#queue-create-playlist-popup-save');
this.setupInput();
}
setupInput(){
//Setup input
this.saveButton.addEventListener('click', this.createPlaylist.bind(this));
this.popup.popupDiv.addEventListener('keydown', this.createPlaylist.bind(this));
}
createPlaylist(event){
//If we clicked or hit enter
if(event.key == null || event.key == "Enter"){
//If we're saving to the channel
if(this.location.value == 'channel'){
//Tell the server to create a new channel playlist
this.client.socket.emit('createChannelPlaylist', {
playlist: this.name.value,
defaultTitles: this.defaultTitles.value.split('\n')
})
}
//Close the popup
this.popup.closePopup();
}
}
}
class schedulePopup{ class schedulePopup{
constructor(event, client, url, title, cb, doc){ constructor(event, client, url, title, cb, doc){
//Set Client //Set Client