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

@ -25,7 +25,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
left: 0;
right: 0;
z-index: 4;
padding: 0.15em 0.3em;
}
#queue-control-offset{
@ -38,6 +37,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
display: flex;
flex-direction: row;
justify-content: space-between;
margin: 0.15em 0.3em;
}
@ -133,6 +133,82 @@ div.dragging-queue-entry{
}
/* 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 */
#queue-control-date{
display: flex;

View file

@ -539,6 +539,25 @@ div.archived p{
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*/
div.altcha{
box-shadow: 4px 4px 1px var(--bg1-alt0) inset;

View file

@ -17,6 +17,9 @@ class queuePanel extends panelObj{
//Autoscroll boolean
this.autoscroll = true;
//Setup child classes
this.playlistManager = new playlistManager(client, panelDocument, this);
//Define non-input event listeners
this.defineListeners();
}
@ -43,13 +46,15 @@ class queuePanel extends panelObj{
//Get main control buttons
this.addMediaButton = this.panelDocument.querySelector('#queue-add-media');
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.queueLockButton = this.panelDocument.querySelector('#queue-lock');
//Get control divs
this.addMediaDiv = this.panelDocument.querySelector('#queue-media-prompts');
this.queueDateDiv = this.panelDocument.querySelector('#queue-control-date');
this.playlistDiv = this.panelDocument.querySelector('#queue-playlist-prompts');
//Get control div elements
//Add Media
@ -70,6 +75,10 @@ class queuePanel extends panelObj{
//Setup panel input
this.setupInput();
//Pass the new panelDoc and docSwitch() call down to child classes
this.playlistManager.panelDocument = this.panelDocument;
this.playlistManager.docSwitch();
}
closer(){
@ -99,6 +108,7 @@ class queuePanel extends panelObj{
this.addMediaButton.addEventListener('click', this.toggleAddMedia.bind(this));
this.scrollLockButton.addEventListener('click', this.lockScroll.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.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){
//Call up the popup
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{
constructor(event, client, url, title, cb, doc){
//Set Client