Finished up with Playlist Managment UI.
This commit is contained in:
parent
c8c59feb7f
commit
f21b66fae0
|
|
@ -35,6 +35,7 @@ module.exports = class{
|
|||
socket.on("getChannelPlaylists", () => {this.getChannelPlaylists(socket)});
|
||||
socket.on("createChannelPlaylist", (data) => {this.createChannelPlaylist(socket, data)});
|
||||
socket.on("deleteChannelPlaylist", (data) => {this.deleteChannelPlaylist(socket, data)});
|
||||
socket.on("deleteChannelPlaylistMedia", (data) => {this.deleteChannelPlaylistMedia(socket, data)});
|
||||
socket.on("addToChannelPlaylist", (data) => {this.addToChannelPlaylist(socket, data)});
|
||||
socket.on("queueChannelPlaylist", (data) => {this.queueChannelPlaylist(socket, data)});
|
||||
socket.on("renameChannelPlaylist", (data) => {this.renameChannelPlaylist(socket, data)});
|
||||
|
|
@ -290,4 +291,30 @@ module.exports = class{
|
|||
return loggerUtils.socketExceptionHandler(socket, err);
|
||||
}
|
||||
}
|
||||
|
||||
async deleteChannelPlaylistMedia(socket, data, chanDB){
|
||||
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});
|
||||
}
|
||||
|
||||
//If we don't have a valid UUID
|
||||
if(!validator.isUUID(data.uuid)){
|
||||
//Bitch, moan, complain...
|
||||
loggerUtils.socketErrorHandler(socket, `'${data.uuid}' is not a valid UUID!`, "validation");
|
||||
//and ignore it!
|
||||
return;
|
||||
}
|
||||
|
||||
//Delete media from channel playlist
|
||||
chanDB.deletePlaylistMediaByUUID(data.playlist, data.uuid);
|
||||
|
||||
//Return playlists from channel doc
|
||||
socket.emit('chanPlaylists', chanDB.getPlaylists());
|
||||
}catch(err){
|
||||
return loggerUtils.socketExceptionHandler(socket, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -606,6 +606,17 @@ channelSchema.methods.deletePlaylistByName = async function(name){
|
|||
await this.save();
|
||||
}
|
||||
|
||||
channelSchema.methods.deletePlaylistMediaByUUID = async function(name, uuid){
|
||||
//Find the playlist
|
||||
let playlist = this.getPlaylistByName(name);
|
||||
|
||||
//splice out the given playlist
|
||||
this.media.playlists[playlist.listIndex].deleteMedia(uuid);
|
||||
|
||||
//save the channel document
|
||||
await this.save();
|
||||
}
|
||||
|
||||
channelSchema.methods.addToPlaylist = async function(name, media){
|
||||
//Find the playlist
|
||||
let playlist = this.getPlaylistByName(name);
|
||||
|
|
|
|||
|
|
@ -52,4 +52,21 @@ playlistSchema.methods.dehydrate = function(){
|
|||
}
|
||||
}
|
||||
|
||||
playlistSchema.methods.deleteMedia = function(uuid){
|
||||
//Create new array to hold list of media to be kept
|
||||
const keptMedia = [];
|
||||
|
||||
//For every piece of media in the current playlist
|
||||
for(let media of this.media){
|
||||
//It isn't the media to be deleted
|
||||
if(media.uuid.toString() != uuid){
|
||||
//Add it to the list to be kept
|
||||
keptMedia.push(media);
|
||||
}
|
||||
}
|
||||
|
||||
//Set playlist media from keptMedia
|
||||
this.media = keptMedia;
|
||||
}
|
||||
|
||||
module.exports = playlistSchema;
|
||||
|
|
@ -297,13 +297,17 @@ span.user-entry{
|
|||
margin: 0;
|
||||
left: 1em;
|
||||
top: 0.6em;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#chat-panel-prompt-autocomplete-filler{
|
||||
visibility: hidden;
|
||||
user-select: none;
|
||||
cursor: auto;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#chat-panel-prompt-autocomplete-display{
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.toke{
|
||||
|
|
|
|||
|
|
@ -185,6 +185,11 @@ div.dragging-queue-entry{
|
|||
flex-direction: row;
|
||||
}
|
||||
|
||||
/* Pass these up to the span to prevent moar dot-drawling */
|
||||
.queue-playlist-title-span p, .queue-playlist-title-span i{
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.queue-playlist-count{
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
|
@ -242,7 +247,15 @@ div.dragging-queue-entry{
|
|||
width: 0.5em;
|
||||
}
|
||||
|
||||
.queue-playlist-media-div{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.queue-playlist-media-delete-icon{
|
||||
height: 0.8em;
|
||||
}
|
||||
|
||||
/* date */
|
||||
#queue-control-date{
|
||||
|
|
|
|||
|
|
@ -560,17 +560,16 @@ div.archived p{
|
|||
color: var(--accent1-alt0);
|
||||
}
|
||||
|
||||
.queue-playlist-control:not(.danger-button):not(:hover).queue-playlist-control:not(.positive-button):not(:hover){
|
||||
.queue-playlist-control:not(.danger-text, .positive-button, .danger-button, :hover){
|
||||
background-color: var(--bg1-alt0);
|
||||
color: var(--accent1);
|
||||
}
|
||||
|
||||
|
||||
.queue-playlist-media-div.not-first{
|
||||
border-top: var(--bg1) solid 1px;
|
||||
}
|
||||
|
||||
.queue-playlist-control:not(.queue-playlist-queue-random-button){
|
||||
.queue-playlist-control.not-first{
|
||||
border-left: var(--accent1-alt0) solid 1px;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -71,9 +71,28 @@ class playlistManager{
|
|||
}
|
||||
}
|
||||
|
||||
//Keeping everything in one function was super lazy when 'this.' exists, ESPECIALLY when writing a class!
|
||||
//Really not sure what I was thinking here, this functions a bit of a stinker, and I'll probably re-write it sooner than later...
|
||||
checkOpenPlaylists(){
|
||||
//If open map is a string, indicating we just renamed a playlist with it's media open
|
||||
if(typeof this.openMap == 'string'){
|
||||
//Create new map to hold status with the new name of the renamed playlist already added
|
||||
this.openMap = new Map([[this.openMap, true]]);
|
||||
}else{
|
||||
//Create new map to hold status
|
||||
this.openMap = new Map();
|
||||
}
|
||||
|
||||
|
||||
//For each container Div rendered
|
||||
for(let containerDiv of this.channelPlaylistDiv.querySelectorAll('.queue-playlist-media-container-div')){
|
||||
//Set whether or not it's visible in the map
|
||||
this.openMap.set(containerDiv.dataset['playlist'], (containerDiv.style.display != 'none'));
|
||||
}
|
||||
}
|
||||
|
||||
renderChannelPlaylists(data){
|
||||
//Check for open playlists
|
||||
this.checkOpenPlaylists();
|
||||
|
||||
//Clear channel playlist div
|
||||
this.channelPlaylistDiv.innerHTML = '';
|
||||
|
||||
|
|
@ -87,9 +106,6 @@ class playlistManager{
|
|||
//Set it's class
|
||||
this.playlistDiv.classList.add('queue-playlist-div');
|
||||
|
||||
//Set playlist div dataset
|
||||
this.playlistDiv.dataset.name = this.playlist.name;
|
||||
|
||||
//Create span to hold playlist entry line contents
|
||||
this.playlistSpan = document.createElement('span');
|
||||
//Set classes
|
||||
|
|
@ -160,27 +176,7 @@ class playlistManager{
|
|||
this.playlistLabels.appendChild(this.playlistCount);
|
||||
|
||||
//Define input listeners
|
||||
this.playlistTitleSpan.addEventListener('click', toggleMedia.bind(this));
|
||||
|
||||
//I'd rather make this a class function but it's probably cleaner to not have to parent crawl
|
||||
function toggleMedia(){
|
||||
//If the div is hidden
|
||||
if(this.mediaContainer.style.display == 'none'){
|
||||
//Light up the button
|
||||
this.playlistTitleSpan.classList.add('positive');
|
||||
//Flip the caret
|
||||
this.playlistTitleCaret.classList.replace('bi-caret-right-fill', 'bi-caret-down-fill');
|
||||
//Show the div
|
||||
this.mediaContainer.style.display = '';
|
||||
}else{
|
||||
//Unlight the button
|
||||
this.playlistTitleSpan.classList.remove('positive');
|
||||
//Flip the caret
|
||||
this.playlistTitleCaret.classList.replace('bi-caret-down-fill', 'bi-caret-right-fill');
|
||||
//Hide the div
|
||||
this.mediaContainer.style.display = 'none';
|
||||
}
|
||||
}
|
||||
this.playlistTitleSpan.addEventListener('click', this.toggleMedia.bind(this));
|
||||
}
|
||||
|
||||
renderControls(){
|
||||
|
|
@ -203,7 +199,7 @@ class playlistManager{
|
|||
//Create queue all button
|
||||
this.playlistQueueAllButton = document.createElement('button');
|
||||
//Set it's classes
|
||||
this.playlistQueueAllButton.classList.add('queue-playlist-queue-all-button', 'queue-playlist-control');
|
||||
this.playlistQueueAllButton.classList.add('queue-playlist-queue-all-button', 'queue-playlist-control', 'not-first');
|
||||
//Inject text content
|
||||
this.playlistQueueAllButton.textContent = 'All';
|
||||
//Set title
|
||||
|
|
@ -212,7 +208,7 @@ class playlistManager{
|
|||
//Create add from URL button
|
||||
this.playlistAddURLButton = document.createElement('button');
|
||||
//Set it's classes
|
||||
this.playlistAddURLButton.classList.add('queue-playlist-add-url-button', 'queue-playlist-control', 'positive-button');
|
||||
this.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'
|
||||
|
||||
|
|
@ -223,13 +219,14 @@ class playlistManager{
|
|||
this.playlistAddIcon.classList.add('bi-plus-lg');
|
||||
this.playlistLinkIcon.classList.add('bi-link-45deg');
|
||||
|
||||
//Append icons to URL button
|
||||
this.playlistAddURLButton.appendChild(this.playlistAddIcon);
|
||||
this.playlistAddURLButton.appendChild(this.playlistLinkIcon);
|
||||
|
||||
//Create default titles button
|
||||
this.playlistDefaultTitlesButton = document.createElement('button');
|
||||
//Set classes
|
||||
this.playlistDefaultTitlesButton.classList.add('queue-playlist-add-url-button', 'queue-playlist-control', 'bi-tags-fill', 'positive-button');
|
||||
this.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'
|
||||
//Set dataset
|
||||
|
|
@ -238,15 +235,14 @@ class playlistManager{
|
|||
//Create rename button
|
||||
this.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');
|
||||
this.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'
|
||||
|
||||
|
||||
//Create delete button
|
||||
this.playlistDeleteButton = document.createElement('button');
|
||||
//Set it's classes
|
||||
this.playlistDeleteButton.classList.add('queue-playlist-delete-button', 'queue-playlist-control', 'danger-button', 'bi-trash-fill');
|
||||
this.playlistDeleteButton.classList.add('queue-playlist-delete-button', 'queue-playlist-control', 'danger-button', 'bi-trash-fill', 'not-first');
|
||||
//Set title
|
||||
this.playlistDeleteButton.title = 'Delete Playlist'
|
||||
|
||||
|
|
@ -271,8 +267,15 @@ class playlistManager{
|
|||
this.mediaContainer = document.createElement('div');
|
||||
//Set classes
|
||||
this.mediaContainer.classList.add('queue-playlist-media-container-div');
|
||||
//Auto-hide media container
|
||||
this.mediaContainer.style.display = 'none';
|
||||
|
||||
//If the playlist wasn't set to open in the open map
|
||||
if(!this.openMap.get(this.playlist.name)){
|
||||
//Auto-hide media container
|
||||
this.mediaContainer.style.display = 'none';
|
||||
}
|
||||
|
||||
//Set dataset
|
||||
this.mediaContainer.dataset['playlist'] = this.playlist.name;
|
||||
|
||||
for(let mediaIndex in this.playlist.media){
|
||||
//Grab media object from playlist
|
||||
|
|
@ -295,8 +298,23 @@ class playlistManager{
|
|||
//Inject text content
|
||||
mediaTitle.innerText = utils.unescapeEntities(media.title);
|
||||
|
||||
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 media from playlist';
|
||||
//Set dataset
|
||||
//It's probably more effecient to set this at mediaContainer level, but I don't want to crawl multiple parents later on
|
||||
deleteMediaIcon.dataset['playlist'] = this.playlist.name;
|
||||
deleteMediaIcon.dataset['uuid'] = media.uuid;
|
||||
|
||||
//Append items to media div
|
||||
mediaDiv.appendChild(mediaTitle);
|
||||
mediaDiv.appendChild(deleteMediaIcon);
|
||||
|
||||
|
||||
//Handle input event listeners
|
||||
deleteMediaIcon.addEventListener('click', this.deleteMedia.bind(this));
|
||||
|
||||
//Append media div to media container
|
||||
this.mediaContainer.appendChild(mediaDiv);
|
||||
|
|
@ -306,6 +324,31 @@ class playlistManager{
|
|||
this.mediaContainer;
|
||||
}
|
||||
|
||||
//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');
|
||||
//I hope my mother doesn't see this next line, god I hate dot crawling...
|
||||
const mediaContainer = event.target.parentNode.parentNode.nextElementSibling;
|
||||
|
||||
//If the div is hidden
|
||||
if(mediaContainer.style.display == 'none'){
|
||||
//Light up the button
|
||||
event.target.classList.add('positive');
|
||||
//Flip the caret
|
||||
playlistTitleCaret.classList.replace('bi-caret-right-fill', 'bi-caret-down-fill');
|
||||
//Show the div
|
||||
mediaContainer.style.display = '';
|
||||
}else{
|
||||
//Unlight the button
|
||||
event.target.classList.remove('positive');
|
||||
//Flip the caret
|
||||
playlistTitleCaret.classList.replace('bi-caret-down-fill', 'bi-caret-right-fill');
|
||||
//Hide the div
|
||||
mediaContainer.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
addURL(event){
|
||||
new addURLPopup(
|
||||
event,
|
||||
|
|
@ -331,16 +374,32 @@ class playlistManager{
|
|||
event,
|
||||
event.target.parentNode.dataset['playlist'],
|
||||
this.client,
|
||||
this.queuePanel.ownerDoc
|
||||
this.queuePanel.ownerDoc,
|
||||
handleOpenedMedia.bind(this)
|
||||
);
|
||||
|
||||
function handleOpenedMedia(newName){
|
||||
//do an ugly dot crawl to get the media container div
|
||||
const mediaContainer = event.target.parentNode.parentNode.nextElementSibling;
|
||||
|
||||
//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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
queueAll(event){
|
||||
client.socket.emit('queueChannelPlaylist', {playlist: event.target.parentNode.dataset['playlist']});
|
||||
this.client.socket.emit('queueChannelPlaylist', {playlist: event.target.parentNode.dataset['playlist']});
|
||||
}
|
||||
|
||||
deletePlaylist(event){
|
||||
client.socket.emit('deleteChannelPlaylist', {playlist: event.target.parentNode.dataset['playlist']});
|
||||
this.client.socket.emit('deleteChannelPlaylist', {playlist: event.target.parentNode.dataset['playlist']});
|
||||
}
|
||||
|
||||
deleteMedia(event){
|
||||
this.client.socket.emit('deleteChannelPlaylistMedia', {playlist: event.target.dataset['playlist'], uuid: event.target.dataset['uuid']});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -478,13 +537,15 @@ class defaultTitlesPopup{
|
|||
}
|
||||
|
||||
class renamePopup{
|
||||
constructor(event, playlist, client, doc){
|
||||
constructor(event, playlist, client, doc, cb){
|
||||
//Set Client
|
||||
this.client = client;
|
||||
|
||||
//Set playlist
|
||||
this.playlist = playlist
|
||||
|
||||
this.cb = cb;
|
||||
|
||||
//Create media popup and call async constructor when done
|
||||
//unfortunately we cant call constructors asyncronously, and we cant call back to this from super, so we can't extend this as it stands :(
|
||||
this.popup = new canopyUXUtils.popup('/renamePlaylist', true, this.asyncConstructor.bind(this), doc);
|
||||
|
|
@ -513,6 +574,12 @@ class renamePopup{
|
|||
name: this.renamePrompt.value
|
||||
});
|
||||
|
||||
//if CB is a function
|
||||
if(typeof this.cb == 'function'){
|
||||
//Hand it back the new name
|
||||
this.cb(this.renamePrompt.value);
|
||||
}
|
||||
|
||||
//Close the popup
|
||||
this.popup.closePopup();
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue