diff --git a/src/views/partial/popup/newPlaylist.ejs b/src/views/partial/popup/newPlaylist.ejs index 196ad3e..d20f32f 100644 --- a/src/views/partial/popup/newPlaylist.ejs +++ b/src/views/partial/popup/newPlaylist.ejs @@ -20,8 +20,8 @@ along with this program. If not, see . %> diff --git a/www/js/channel/panels/queuePanel/playlistManager.js b/www/js/channel/panels/queuePanel/playlistManager.js index 6d6d51c..77269c9 100644 --- a/www/js/channel/panels/queuePanel/playlistManager.js +++ b/www/js/channel/panels/queuePanel/playlistManager.js @@ -21,6 +21,11 @@ class playlistManager{ this.panelDocument = panelDocument; //Set parent queue panel this.queuePanel = queuePanel; + //Create openMap + this.openMap = { + Channel: new Map(), + User: new Map() + }; //Define Listeners this.defineListeners(); @@ -28,6 +33,7 @@ class playlistManager{ defineListeners(){ this.client.socket.on("chanPlaylists", this.renderChannelPlaylists.bind(this)); + this.client.socket.on("userPlaylists", this.renderUserPlaylists.bind(this)); } docSwitch(){ @@ -44,6 +50,7 @@ class playlistManager{ //Force playlist re-render to fix controls this.client.socket.emit('getChannelPlaylists'); + this.client.socket.emit('getUserPlaylists'); //Setup Input this.setupInput(); @@ -95,228 +102,263 @@ class playlistManager{ } } - checkOpenPlaylists(){ + checkOpenPlaylists(location){ //If open map is a string, indicating we just renamed a playlist with it's media open - if(typeof this.openMap == 'string'){ + if(typeof this.openMap[location] == 'string'){ //Create new map to hold status with the new name of the renamed playlist already added - this.openMap = new Map([[this.openMap, true]]); + this.openMap[location] = new Map([[this.openMap[location], true]]); }else{ //Create new map to hold status - this.openMap = new Map(); + this.openMap[location] = new Map(); } + let mediaContainerDivs = []; + + if(location == 'Channel'){ + mediaContainerDivs = this.channelPlaylistDiv.querySelectorAll('.queue-playlist-media-container-div') + }else{ + mediaContainerDivs = this.userPlaylistDiv.querySelectorAll('.queue-playlist-media-container-div') + } //For each container Div rendered - for(let containerDiv of this.channelPlaylistDiv.querySelectorAll('.queue-playlist-media-container-div')){ + for(let containerDiv of mediaContainerDivs){ //Set whether or not it's visible in the map - this.openMap.set(containerDiv.dataset['playlist'], (containerDiv.style.display != 'none')); + this.openMap[location].set(containerDiv.dataset['playlist'], (containerDiv.style.display != 'none')); } } + //Main playlist rendering functions renderChannelPlaylists(data){ //Check for open playlists - this.checkOpenPlaylists(); + this.checkOpenPlaylists('Channel'); + + console.log(this.openMap); //Clear channel playlist div this.channelPlaylistDiv.innerHTML = ''; + //Append rendered playlists + this.channelPlaylistDiv.append(...this.renderPlaylists(data, 'Channel')); + } + + renderUserPlaylists(data){ + //Check for open playlists + this.checkOpenPlaylists('User'); + + //Clear channel playlist div + this.userPlaylistDiv.innerHTML = ''; + + //Append rendered playlists + this.userPlaylistDiv.append(...this.renderPlaylists(data, 'User')); + } + + renderPlaylists(data, location){ + const playlists = []; + //For every playlist sent down from the server for(let playlistIndex in data){ //Get playlist from data - this.playlist = data[playlistIndex]; + const playlist = data[playlistIndex]; //Create a new playlist div - this.playlistDiv = document.createElement('div'); + const playlistDiv = document.createElement('div'); //Set it's class - this.playlistDiv.classList.add('queue-playlist-div'); + playlistDiv.classList.add('queue-playlist-div'); //Create span to hold playlist entry line contents - this.playlistSpan = document.createElement('span'); + const playlistSpan = document.createElement('span'); //Set classes - this.playlistSpan.classList.add('queue-playlist-span'); + playlistSpan.classList.add('queue-playlist-span'); //If this isn't our first rodeo - if(this.playlistIndex != 0){ + if(playlistIndex != 0){ //make note - this.playlistSpan.classList.add('not-first'); + playlistSpan.classList.add('not-first'); } - //Render out playlist entry contents - this.renderLabels(); - this.renderControls(); - this.renderMedia(); + //assemble playlist entry line + playlistSpan.append( + this.renderLabels(playlist, location), + this.renderControls(playlist, location) + ); - //Append entry items to playlist entry line - this.playlistSpan.appendChild(this.playlistLabels); - this.playlistSpan.appendChild(this.playlistControls); + //assemble playlist div + playlistDiv.append( + playlistSpan, + this.renderMedia(playlist, location), + ); - //Append entry line and contents to playlist div - this.playlistDiv.appendChild(this.playlistSpan); - this.playlistDiv.appendChild(this.mediaContainer); - - //Append current playlist div to the channel playlists div - this.channelPlaylistDiv.appendChild(this.playlistDiv); + //add playlist div to playlists array + playlists.push(playlistDiv); } + return playlists; } //aux rendering functions - renderLabels(){ + renderLabels(playlist, location){ //Create playlist label span - this.playlistLabels = document.createElement('span'); + const playlistLabels = document.createElement('span'); //Set it's class - this.playlistLabels.classList.add('queue-playlist-labels-span'); + playlistLabels.classList.add('queue-playlist-labels-span'); //create playlist title span - this.playlistTitleSpan = document.createElement('span'); + const playlistTitleSpan = document.createElement('span'); //Set class - this.playlistTitleSpan.classList.add('queue-playlist-title-span', 'interactive'); + playlistTitleSpan.classList.add('queue-playlist-title-span', 'interactive'); //Create playlist title caret - this.playlistTitleCaret = document.createElement('i'); + const playlistTitleCaret = document.createElement('i'); //If this is supposed to be open - if(this.openMap.get(this.playlist.name)){ + if(this.openMap[location].get(playlist.name)){ //Set class accordingly - this.playlistTitleSpan.classList.add('positive'); - this.playlistTitleCaret.classList.add('bi-caret-down-fill'); + playlistTitleSpan.classList.add('positive'); + playlistTitleCaret.classList.add('bi-caret-down-fill'); //otherwise }else{ //Set class accordingly - this.playlistTitleCaret.classList.add('bi-caret-right-fill'); + playlistTitleCaret.classList.add('bi-caret-right-fill'); } //Create playlist title label - this.playlistTitle = document.createElement('p'); + const playlistTitle = document.createElement('p'); //Set it's class - this.playlistTitle.classList.add('queue-playlist-title'); + playlistTitle.classList.add('queue-playlist-title'); //Unescape Sanatized Enteties and safely inject as plaintext - this.playlistTitle.innerText = utils.unescapeEntities(this.playlist.name); + playlistTitle.innerText = utils.unescapeEntities(playlist.name); //Construct playlist title span - this.playlistTitleSpan.appendChild(this.playlistTitleCaret); - this.playlistTitleSpan.appendChild(this.playlistTitle); + playlistTitleSpan.appendChild(playlistTitleCaret); + playlistTitleSpan.appendChild(playlistTitle); //Create playlist count label - this.playlistCount = document.createElement('p'); + const playlistCount = document.createElement('p'); //Set it's class - this.playlistCount.classList.add('queue-playlist-count'); + playlistCount.classList.add('queue-playlist-count'); //List video count - this.playlistCount.innerText = `Count: ${this.playlist.media.length}`; + playlistCount.innerText = `Count: ${playlist.media.length}`; //Append items to playlist labels span - this.playlistLabels.appendChild(this.playlistTitleSpan); - this.playlistLabels.appendChild(this.playlistCount); + playlistLabels.appendChild(playlistTitleSpan); + playlistLabels.appendChild(playlistCount); //Define input listeners - this.playlistTitleSpan.addEventListener('click', this.toggleMedia.bind(this)); + playlistTitleSpan.addEventListener('click', this.toggleMedia.bind(this)); + + return playlistLabels; } - renderControls(){ + renderControls(playlist, location){ //Create playlist control span - this.playlistControls = document.createElement('span'); + const playlistControls = document.createElement('span'); //Set it's class - this.playlistControls.classList.add('queue-playlist-control-span'); + playlistControls.classList.add('queue-playlist-control-span'); //Set dataset - this.playlistControls.dataset['playlist'] = this.playlist.name; + playlistControls.dataset['playlist'] = playlist.name; + playlistControls.dataset['location'] = location; //Create queue all button - this.playlistQueueRandomButton = document.createElement('button'); + const playlistQueueRandomButton = document.createElement('button'); //Set it's classes - this.playlistQueueRandomButton.classList.add('queue-playlist-queue-random-button', 'queue-playlist-control'); + playlistQueueRandomButton.classList.add('queue-playlist-queue-random-button', 'queue-playlist-control'); //Inject text content - this.playlistQueueRandomButton.textContent = 'Random'; + playlistQueueRandomButton.textContent = 'Random'; //Set title - this.playlistQueueRandomButton.title = 'Queue Random Item from Playlist'; + playlistQueueRandomButton.title = 'Queue Random Item from Playlist'; //Create queue all button - this.playlistQueueAllButton = document.createElement('button'); + const playlistQueueAllButton = document.createElement('button'); //Set it's classes - this.playlistQueueAllButton.classList.add('queue-playlist-queue-all-button', 'queue-playlist-control', 'not-first'); + playlistQueueAllButton.classList.add('queue-playlist-queue-all-button', 'queue-playlist-control', 'not-first'); //Inject text content - this.playlistQueueAllButton.textContent = 'All'; + playlistQueueAllButton.textContent = 'All'; //Set title - this.playlistQueueAllButton.title = 'Queue Entire Playlist'; + playlistQueueAllButton.title = 'Queue Entire Playlist'; //Create add from URL button - this.playlistAddURLButton = document.createElement('button'); + const playlistAddURLButton = document.createElement('button'); //Set it's classes - this.playlistAddURLButton.classList.add('queue-playlist-add-url-button', 'queue-playlist-control', 'positive-button', 'not-first'); + playlistAddURLButton.classList.add('queue-playlist-add-url-button', 'queue-playlist-control', 'positive-button', 'not-first'); //Set Tile - this.playlistAddURLButton.title = 'Add To Playlist From URL' + playlistAddURLButton.title = 'Add To Playlist From URL' //Create playlist icons (we're using two so we're putting them inside the button :P) - this.playlistAddIcon = document.createElement('i'); - this.playlistLinkIcon = document.createElement('i'); + const playlistAddIcon = document.createElement('i'); + const playlistLinkIcon = document.createElement('i'); //set classes - this.playlistAddIcon.classList.add('bi-plus-lg'); - this.playlistLinkIcon.classList.add('bi-link-45deg'); + playlistAddIcon.classList.add('bi-plus-lg'); + playlistLinkIcon.classList.add('bi-link-45deg'); //Append icons to URL button - this.playlistAddURLButton.appendChild(this.playlistAddIcon); - this.playlistAddURLButton.appendChild(this.playlistLinkIcon); + playlistAddURLButton.appendChild(playlistAddIcon); + playlistAddURLButton.appendChild(playlistLinkIcon); //Create default titles button - this.playlistDefaultTitlesButton = document.createElement('button'); + const playlistDefaultTitlesButton = document.createElement('button'); //Set classes - this.playlistDefaultTitlesButton.classList.add('queue-playlist-add-url-button', 'queue-playlist-control', 'bi-tags-fill', 'positive-button', 'not-first'); + playlistDefaultTitlesButton.classList.add('queue-playlist-add-url-button', 'queue-playlist-control', 'bi-tags-fill', 'positive-button', 'not-first'); //Set title - this.playlistDefaultTitlesButton.title = 'Change Default Titles' + playlistDefaultTitlesButton.title = 'Change Default Titles' //Set dataset - this.playlistDefaultTitlesButton.dataset['titles'] = JSON.stringify(this.playlist.defaultTitles); + playlistDefaultTitlesButton.dataset['titles'] = JSON.stringify(playlist.defaultTitles); //Create rename button - this.playlistRenameButton = document.createElement('button'); + const playlistRenameButton = document.createElement('button'); //Set it's classes - this.playlistRenameButton.classList.add('queue-playlist-add-url-button', 'queue-playlist-control', 'bi-input-cursor-text', 'positive-button', 'not-first'); + playlistRenameButton.classList.add('queue-playlist-add-url-button', 'queue-playlist-control', 'bi-input-cursor-text', 'positive-button', 'not-first'); //Set title - this.playlistRenameButton.title = 'Rename Playlist' + playlistRenameButton.title = 'Rename Playlist' //Create delete button - this.playlistDeleteButton = document.createElement('button'); + const playlistDeleteButton = document.createElement('button'); //Set it's classes - this.playlistDeleteButton.classList.add('queue-playlist-delete-button', 'queue-playlist-control', 'danger-button', 'bi-trash-fill', 'not-first'); + playlistDeleteButton.classList.add('queue-playlist-delete-button', 'queue-playlist-control', 'danger-button', 'bi-trash-fill', 'not-first'); //Set title - this.playlistDeleteButton.title = 'Delete Playlist' + playlistDeleteButton.title = 'Delete Playlist' //Append items to playlist control span - this.playlistControls.appendChild(this.playlistQueueRandomButton); - this.playlistControls.appendChild(this.playlistQueueAllButton); - this.playlistControls.appendChild(this.playlistAddURLButton); - this.playlistControls.appendChild(this.playlistDefaultTitlesButton); - this.playlistControls.appendChild(this.playlistRenameButton); - this.playlistControls.appendChild(this.playlistDeleteButton); + playlistControls.append( + playlistQueueRandomButton, + playlistQueueAllButton, + playlistAddURLButton, + playlistDefaultTitlesButton, + playlistRenameButton, + playlistDeleteButton + ); //Define input event listeners - this.playlistAddURLButton.addEventListener('click', this.addURL.bind(this)); - this.playlistDefaultTitlesButton.addEventListener('click', this.editDefaultTitles.bind(this)); - this.playlistRenameButton.addEventListener('click', this.renamePlaylist.bind(this)); - this.playlistQueueRandomButton.addEventListener('click', this.queueRandom.bind(this)); - this.playlistQueueAllButton.addEventListener('click', this.queueAll.bind(this)); - this.playlistDeleteButton.addEventListener('click', this.deletePlaylist.bind(this)); + playlistAddURLButton.addEventListener('click', this.addURL.bind(this)); + playlistDefaultTitlesButton.addEventListener('click', this.editDefaultTitles.bind(this)); + playlistRenameButton.addEventListener('click', this.renamePlaylist.bind(this)); + playlistQueueRandomButton.addEventListener('click', this.queueRandom.bind(this)); + playlistQueueAllButton.addEventListener('click', this.queueAll.bind(this)); + playlistDeleteButton.addEventListener('click', this.deletePlaylist.bind(this)); + + return playlistControls; } - renderMedia(){ + renderMedia(playlist, location){ //Create media container div - this.mediaContainer = document.createElement('div'); + const mediaContainer = document.createElement('div'); //Set classes - this.mediaContainer.classList.add('queue-playlist-media-container-div'); + mediaContainer.classList.add('queue-playlist-media-container-div'); //If the playlist wasn't set to open in the open map - if(!this.openMap.get(this.playlist.name)){ + if(!this.openMap[location].get(playlist.name)){ //Auto-hide media container - this.mediaContainer.style.display = 'none'; + mediaContainer.style.display = 'none'; } //Set dataset - this.mediaContainer.dataset['playlist'] = this.playlist.name; + mediaContainer.dataset['playlist'] = playlist.name; - for(let mediaIndex in this.playlist.media){ + for(let mediaIndex in playlist.media){ //Grab media object from playlist - this.media = this.playlist.media[mediaIndex]; + const media = playlist.media[mediaIndex]; //Sanatize title text - const title = utils.unescapeEntities(this.media.title); + const title = utils.unescapeEntities(media.title); //Create media div const mediaDiv = document.createElement('div'); @@ -338,56 +380,57 @@ class playlistManager{ //Inject text content mediaTitle.innerText = title; - //Render out media controls - this.renderMediaControls(); - //Append items to media div - mediaDiv.appendChild(mediaTitle); - mediaDiv.appendChild(this.mediaControlSpan); + mediaDiv.append( + mediaTitle, + this.renderMediaControls(media, playlist, location) + ); //Append media div to media container - this.mediaContainer.appendChild(mediaDiv); + mediaContainer.appendChild(mediaDiv); } //return media container - this.mediaContainer; + return mediaContainer; } - renderMediaControls(){ + renderMediaControls(media, playlist, location){ //Create media control span - this.mediaControlSpan = document.createElement('span'); + const mediaControlSpan = document.createElement('span'); //Set it's class - this.mediaControlSpan.classList.add('queue-playlist-media-control-span'); + mediaControlSpan.classList.add('queue-playlist-media-control-span'); //Set dataset - this.mediaControlSpan.dataset['playlist'] = this.playlist.name; - this.mediaControlSpan.dataset['uuid'] = this.media.uuid; - + mediaControlSpan.dataset['playlist'] = playlist.name; + mediaControlSpan.dataset['uuid'] = media.uuid; + mediaControlSpan.dataset['location'] = location; //Create Queue Media icon const queueMediaIcon = document.createElement('i'); //set class queueMediaIcon.classList.add('queue-playlist-control', 'queue-playlist-media-queue-icon', 'bi-play-circle'); //Set title - queueMediaIcon.title = (`Queue '${this.media.title}'`); + queueMediaIcon.title = (`Queue '${media.title}'`); //Create delete media icon const deleteMediaIcon = document.createElement('i'); //set class deleteMediaIcon.classList.add('queue-playlist-control', 'queue-playlist-media-delete-icon', 'danger-text', 'bi-trash-fill'); //Set title - deleteMediaIcon.title = `Delete '${this.media.title}' from playlist '${this.playlist.name}'`; + deleteMediaIcon.title = `Delete '${media.title}' from playlist '${playlist.name}'`; //Append items to media control span - this.mediaControlSpan.appendChild(queueMediaIcon); - this.mediaControlSpan.appendChild(deleteMediaIcon); + mediaControlSpan.appendChild(queueMediaIcon); + mediaControlSpan.appendChild(deleteMediaIcon); //Handle input event listeners queueMediaIcon.addEventListener('click', this.queueMedia.bind(this)); deleteMediaIcon.addEventListener('click', this.deleteMedia.bind(this)); + + //Return media control span + return mediaControlSpan; } - //I'd rather make this a class function but it's probably cleaner to not have to parent crawl toggleMedia(event){ //Grab playlist title caret const playlistTitleCaret = event.target.querySelector('i'); @@ -413,9 +456,11 @@ class playlistManager{ } addURL(event){ + console.log(event.target.parentNode.dataset['location']); new addURLPopup( event, event.target.parentNode.dataset['playlist'], + event.target.parentNode.dataset['location'], this.client, this.queuePanel.ownerDoc ); @@ -427,6 +472,7 @@ class playlistManager{ event, event.target.parentNode.dataset['playlist'], JSON.parse(event.target.dataset['titles']), + event.target.parentNode.dataset['location'], this.client, this.queuePanel.ownerDoc ); @@ -448,29 +494,29 @@ class playlistManager{ //If the media container is visible if(mediaContainer.style.display != 'none'){ //Set openMap to new name indicating the new playlist has it's media opened - this.openMap = newName; + this.openMap[event.target.parentNode.dataset['location']] = newName; } } } queueAll(event){ - this.client.socket.emit('queueChannelPlaylist', {playlist: event.target.parentNode.dataset['playlist']}); + this.client.socket.emit(`queue${event.target.parentNode.dataset['location']}Playlist`, {playlist: event.target.parentNode.dataset['playlist']}); } queueMedia(event){ - this.client.socket.emit('queueFromChannelPlaylist',{playlist: event.target.parentNode.dataset['playlist'], uuid: event.target.parentNode.dataset['uuid']}); + this.client.socket.emit(`queueFrom${event.target.parentNode.dataset['location']}Playlist`,{playlist: event.target.parentNode.dataset['playlist'], uuid: event.target.parentNode.dataset['uuid']}); } queueRandom(event){ - this.client.socket.emit('queueRandomFromChannelPlaylist',{playlist: event.target.parentNode.dataset['playlist']}); + this.client.socket.emit(`queueRandomFrom${event.target.parentNode.dataset['location']}Playlist`,{playlist: event.target.parentNode.dataset['playlist']}); } deletePlaylist(event){ - this.client.socket.emit('deleteChannelPlaylist', {playlist: event.target.parentNode.dataset['playlist']}); + this.client.socket.emit(`delete${event.target.parentNode.dataset['location']}Playlist`, {playlist: event.target.parentNode.dataset['playlist']}); } - deleteMedia(event){ - this.client.socket.emit('deleteChannelPlaylistMedia', {playlist: event.target.parentNode.dataset['playlist'], uuid: event.target.parentNode.dataset['uuid']}); + deleteMedia(event ){ + this.client.socket.emit(`delete${event.target.parentNode.dataset['location']}PlaylistMedia`, {playlist: event.target.parentNode.dataset['playlist'], uuid: event.target.parentNode.dataset['uuid']}); } } @@ -504,14 +550,11 @@ class newPlaylistPopup{ //If we clicked or hit enter if(event.key == null || (event.key == "Enter" && this.defaultTitles !== this.popup.doc.activeElement)){ - //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') - }) - } + //Tell the server to create a new playlist + this.client.socket.emit(`create${this.location.value}Playlist`, { + playlist: this.name.value, + defaultTitles: this.defaultTitles.value.split('\n') + }); //Close the popup this.popup.closePopup(); @@ -520,13 +563,16 @@ class newPlaylistPopup{ } class addURLPopup{ - constructor(event, playlist, client, doc){ + constructor(event, playlist, location, client, doc){ //Set Client this.client = client; //Set playlist this.playlist = playlist + //Set location + this.location = location; + //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('/addToPlaylist', true, this.asyncConstructor.bind(this), doc); @@ -550,7 +596,8 @@ class addURLPopup{ if(event.key == null || event.key == "Enter"){ //Tell the server to add url to the playlist - this.client.socket.emit('addToChannelPlaylist', { + this.client.socket.emit(`addTo${this.location}Playlist`, { + //this.client.socket.emit(`addToChannelPlaylist`, { playlist: this.playlist, url: this.urlPrompt.value }); @@ -562,13 +609,16 @@ class addURLPopup{ } class defaultTitlesPopup{ - constructor(event, playlist, titles, client, doc){ + constructor(event, playlist, titles, location, client, doc){ //Set Client this.client = client; //Set playlist this.playlist = playlist + //Set location + this.location = location; + //Set title string this.titles = titles.join('\n'); @@ -596,7 +646,7 @@ class defaultTitlesPopup{ if(event.key == null || (event.key == "Enter" && this.titlePrompt !== this.popup.doc.activeElement)){ //Tell the server to change the titles - this.client.socket.emit('changeDefaultTitlesChannelPlaylist', { + this.client.socket.emit(`changeDefaultTitles${this.location}Playlist`, { playlist: this.playlist, defaultTitles: this.titlePrompt.value.split('\n') }); @@ -615,6 +665,7 @@ class renamePopup{ //Set playlist this.playlist = playlist + //Set callback this.cb = cb; //Create media popup and call async constructor when done