diff --git a/src/views/channel.ejs b/src/views/channel.ejs
index f923223..8bdea7a 100644
--- a/src/views/channel.ejs
+++ b/src/views/channel.ejs
@@ -50,6 +50,7 @@ along with this program. If not, see . %>
<%# panels %>
+
<%# main client %>
diff --git a/src/views/partial/popup/addToPlaylist.ejs b/src/views/partial/popup/addToPlaylist.ejs
new file mode 100644
index 0000000..8c875c3
--- /dev/null
+++ b/src/views/partial/popup/addToPlaylist.ejs
@@ -0,0 +1,21 @@
+<%# 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 . %>
+<%# %>
+
Add to Playlist From URL
+
+
+
+
\ No newline at end of file
diff --git a/www/css/panel/queue.css b/www/css/panel/queue.css
index a88f22e..37c861d 100644
--- a/www/css/panel/queue.css
+++ b/www/css/panel/queue.css
@@ -224,6 +224,26 @@ div.dragging-queue-entry{
resize: vertical;
}
+.queue-playlist-media-div{
+ margin: 0 0.15em;
+ padding: 0 0.15em;
+}
+
+
+
+.queue-playlist-add-url-button i.bi-link-45deg{
+ margin-right: 0.5em;
+}
+
+.queue-playlist-add-url-button i{
+ display: inline-block;
+ margin: 0;
+ padding: 0;
+ width: 0.5em;
+}
+
+
+
/* date */
#queue-control-date{
display: flex;
diff --git a/www/css/theme/movie-night.css b/www/css/theme/movie-night.css
index a7534ad..07c61cf 100644
--- a/www/css/theme/movie-night.css
+++ b/www/css/theme/movie-night.css
@@ -560,11 +560,20 @@ div.archived p{
color: var(--accent1-alt0);
}
-.queue-playlist-control:not(.danger-button):not(:hover){
+.queue-playlist-control:not(.danger-button):not(:hover).queue-playlist-control:not(.positive-button):not(: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){
+ border-left: var(--accent1-alt0) solid 1px;
+}
+
/* altcha theming*/
div.altcha{
box-shadow: 4px 4px 1px var(--bg1-alt0) inset;
diff --git a/www/js/channel/panels/queuePanel.js b/www/js/channel/panels/queuePanel.js
index 9d436ce..24a1854 100644
--- a/www/js/channel/panels/queuePanel.js
+++ b/www/js/channel/panels/queuePanel.js
@@ -1,3 +1,18 @@
+/*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 .*/
class queuePanel extends panelObj{
constructor(client, panelDocument){
//Call derived constructor
@@ -1031,296 +1046,6 @@ 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 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;
-
- //Create span to hold playlist entry line contents
- const playlistSpan = document.createElement('span');
- //Set classes
- playlistSpan.classList.add('queue-playlist-span');
-
- //If this isn't our first rodeo
- if(playlistIndex != 0){
- //make note
- playlistSpan.classList.add('not-first');
- }
-
- //pre-render and keep this so we can use it later
- const mediaContainer = renderMedia();
-
- //Append items to playlist entry line
- playlistSpan.appendChild(renderLabels());
- playlistSpan.appendChild(renderControls());
-
- //Append items to playlist div
- playlistDiv.appendChild(playlistSpan);
- playlistDiv.appendChild(mediaContainer);
-
- //Append current playlist div to the channel playlists div
- this.channelPlaylistDiv.appendChild(playlistDiv);
-
- //aux rendering functions
- function renderLabels(){
- //Create playlist label span
- const playlistLabels = document.createElement('span');
- //Set it's class
- playlistLabels.classList.add('queue-playlist-labels-span');
-
- //create playlist title span
- const playlistTitleSpan = document.createElement('span');
- //Set class
- playlistTitleSpan.classList.add('queue-playlist-title-span', 'interactive');
-
- //Create playlist title caret
- const playlistTitleCaret = document.createElement('i');
- //Set class
- playlistTitleCaret.classList.add('bi-caret-right-fill');
-
- //Create playlist title label
- const playlistTitle = document.createElement('p');
- //Set it's class
- playlistTitle.classList.add('queue-playlist-title');
- //Unescape Sanatized Enteties and safely inject as plaintext
- playlistTitle.innerText = utils.unescapeEntities(playlist.name);
-
- //Construct playlist title span
- playlistTitleSpan.appendChild(playlistTitleCaret);
- playlistTitleSpan.appendChild(playlistTitle);
-
- //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}`;
-
- //Append items to playlist labels span
- playlistLabels.appendChild(playlistTitleSpan);
- playlistLabels.appendChild(playlistCount);
-
- //Define input listeners
- playlistTitleSpan.addEventListener('click', toggleMedia.bind(this));
-
- //return playlistLabels
- return playlistLabels;
-
- function toggleMedia(){
- //If the div is hidden
- if(mediaContainer.style.display == 'none'){
- //Light up the button
- playlistTitleSpan.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
- playlistTitleSpan.classList.remove('positive');
- //Flip the caret
- playlistTitleCaret.classList.replace('bi-caret-down-fill', 'bi-caret-right-fill');
- //Hide the div
- mediaContainer.style.display = 'none';
- }
- }
- }
-
- function renderControls(){
- //Create playlist control span
- const playlistControls = document.createElement('span');
- //Set it's class
- playlistControls.classList.add('queue-playlist-control-span');
-
- //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');
-
- //Append items to playlist control span
- playlistControls.appendChild(playlistQueueRandomButton);
- playlistControls.appendChild(playlistQueueAllButton);
- playlistControls.appendChild(playlistDeleteButton);
-
- //Define input event listeners
- playlistQueueAllButton.addEventListener('click', queueAll);
- playlistDeleteButton.addEventListener('click', deletePlaylist);
-
- return playlistControls;
- }
-
- function renderMedia(){
- //Create media container div
- const mediaContainer = document.createElement('div');
- //Set classes
- mediaContainer.classList.add('queue-playlist-media-container-div');
- //Auto-hide media container
- mediaContainer.style.display = 'none';
-
- for(let media of playlist.media){
- //Create media div
- const mediaDiv = document.createElement('div');
- //Set class
- mediaDiv.classList.add('queue-playlist-media-div');
-
- //Create media title
- const mediaTitle = document.createElement('p');
- //Set class
- mediaTitle.classList.add('queue-playlist-media-title');
- //Inject text content
- mediaTitle.innerText = utils.unescapeEntities(media.title);
-
- //Append items to media div
- mediaDiv.appendChild(mediaTitle);
-
- //Append media div to media container
- mediaContainer.appendChild(mediaDiv);
- }
-
- //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
diff --git a/www/js/channel/panels/queuePanel/playlistManager.js b/www/js/channel/panels/queuePanel/playlistManager.js
new file mode 100644
index 0000000..32b5af4
--- /dev/null
+++ b/www/js/channel/panels/queuePanel/playlistManager.js
@@ -0,0 +1,441 @@
+/*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 .*/
+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';
+ }
+ }
+
+ //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...
+ 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 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;
+
+ //Create span to hold playlist entry line contents
+ const playlistSpan = document.createElement('span');
+ //Set classes
+ playlistSpan.classList.add('queue-playlist-span');
+
+ //If this isn't our first rodeo
+ if(playlistIndex != 0){
+ //make note
+ playlistSpan.classList.add('not-first');
+ }
+
+ //pre-render and keep this so we can use it later
+ const mediaContainer = renderMedia();
+
+ //Append items to playlist entry line
+ playlistSpan.appendChild((renderLabels.bind(this))());
+ playlistSpan.appendChild((renderControls.bind(this))());
+
+ //Append items to playlist div
+ playlistDiv.appendChild(playlistSpan);
+ playlistDiv.appendChild(mediaContainer);
+
+ //Append current playlist div to the channel playlists div
+ this.channelPlaylistDiv.appendChild(playlistDiv);
+
+ //aux rendering functions
+ function renderLabels(){
+ //Create playlist label span
+ const playlistLabels = document.createElement('span');
+ //Set it's class
+ playlistLabels.classList.add('queue-playlist-labels-span');
+
+ //create playlist title span
+ const playlistTitleSpan = document.createElement('span');
+ //Set class
+ playlistTitleSpan.classList.add('queue-playlist-title-span', 'interactive');
+
+ //Create playlist title caret
+ const playlistTitleCaret = document.createElement('i');
+ //Set class
+ playlistTitleCaret.classList.add('bi-caret-right-fill');
+
+ //Create playlist title label
+ const playlistTitle = document.createElement('p');
+ //Set it's class
+ playlistTitle.classList.add('queue-playlist-title');
+ //Unescape Sanatized Enteties and safely inject as plaintext
+ playlistTitle.innerText = utils.unescapeEntities(playlist.name);
+
+ //Construct playlist title span
+ playlistTitleSpan.appendChild(playlistTitleCaret);
+ playlistTitleSpan.appendChild(playlistTitle);
+
+ //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}`;
+
+ //Append items to playlist labels span
+ playlistLabels.appendChild(playlistTitleSpan);
+ playlistLabels.appendChild(playlistCount);
+
+ //Define input listeners
+ playlistTitleSpan.addEventListener('click', toggleMedia.bind(this));
+
+ //return playlistLabels
+ return playlistLabels;
+
+ function toggleMedia(){
+ //If the div is hidden
+ if(mediaContainer.style.display == 'none'){
+ //Light up the button
+ playlistTitleSpan.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
+ playlistTitleSpan.classList.remove('positive');
+ //Flip the caret
+ playlistTitleCaret.classList.replace('bi-caret-down-fill', 'bi-caret-right-fill');
+ //Hide the div
+ mediaContainer.style.display = 'none';
+ }
+ }
+ }
+
+ function renderControls(){
+ //Create playlist control span
+ const playlistControls = document.createElement('span');
+ //Set it's class
+ playlistControls.classList.add('queue-playlist-control-span');
+
+ //Create queue all button
+ const playlistQueueRandomButton = document.createElement('button');
+ //Set it's classes
+ playlistQueueRandomButton.classList.add('queue-playlist-queue-random-button', 'queue-playlist-control');
+ //Inject text content
+ playlistQueueRandomButton.textContent = 'Random';
+ //Set title
+ playlistQueueRandomButton.title = 'Queue Random Item from Playlist';
+
+ //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 = 'All';
+ //Set title
+ playlistQueueAllButton.title = 'Queue Entire Playlist';
+
+ //Create add from URL button
+ const playlistAddURLButton = document.createElement('button');
+ //Set it's classes
+ playlistAddURLButton.classList.add('queue-playlist-add-url-button', 'queue-playlist-control', 'positive-button');
+ //Set Tile
+ playlistAddURLButton.title = 'Add To Playlist From URL'
+
+ //Create playlist icons (we're using two so we're putting them inside the button :P)
+ const playlistAddIcon = document.createElement('i');
+ const playlistLinkIcon = document.createElement('i');
+ //set classes
+ playlistAddIcon.classList.add('bi-plus-lg');
+ playlistLinkIcon.classList.add('bi-link-45deg');
+
+ playlistAddURLButton.appendChild(playlistAddIcon);
+ playlistAddURLButton.appendChild(playlistLinkIcon);
+
+ //Create default titles button
+ const playlistDefaultTitlesButton = document.createElement('button');
+ //Set classes
+ playlistDefaultTitlesButton.classList.add('queue-playlist-add-url-button', 'queue-playlist-control', 'bi-tags-fill', 'positive-button');
+ //Set title
+ playlistDefaultTitlesButton.title = 'Change Default Titles'
+
+ //Create rename button
+ const playlistRenameButton = document.createElement('button');
+ //Set it's classes
+ playlistRenameButton.classList.add('queue-playlist-add-url-button', 'queue-playlist-control', 'bi-input-cursor-text', 'positive-button');
+ //Set title
+ playlistRenameButton.title = 'Rename Playlist'
+
+
+ //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');
+ //Set title
+ playlistDeleteButton.title = 'Delete Playlist'
+
+ //Append items to playlist control span
+ playlistControls.appendChild(playlistQueueRandomButton);
+ playlistControls.appendChild(playlistQueueAllButton);
+ playlistControls.appendChild(playlistAddURLButton);
+ playlistControls.appendChild(playlistDefaultTitlesButton);
+ playlistControls.appendChild(playlistRenameButton);
+ playlistControls.appendChild(playlistDeleteButton);
+
+ //Define input event listeners
+ playlistAddURLButton.addEventListener('click', (event)=>{new addURLPopup(event, playlist.name, this.client, this.queuePanel.ownerDoc)})
+ playlistQueueAllButton.addEventListener('click', queueAll);
+ playlistDeleteButton.addEventListener('click', deletePlaylist);
+
+ return playlistControls;
+ }
+
+ function renderMedia(){
+ //Create media container div
+ const mediaContainer = document.createElement('div');
+ //Set classes
+ mediaContainer.classList.add('queue-playlist-media-container-div');
+ //Auto-hide media container
+ mediaContainer.style.display = 'none';
+
+ for(let mediaIndex in playlist.media){
+ //Grab media object from playlist
+ const media = playlist.media[mediaIndex];
+
+ //Create media div
+ const mediaDiv = document.createElement('div');
+ //Set class
+ mediaDiv.classList.add('queue-playlist-media-div');
+
+ //If this isn't our first rodeo
+ if(mediaIndex != 0){
+ mediaDiv.classList.add('not-first');
+ }
+
+ //Create media title
+ const mediaTitle = document.createElement('p');
+ //Set class
+ mediaTitle.classList.add('queue-playlist-media-title');
+ //Inject text content
+ mediaTitle.innerText = utils.unescapeEntities(media.title);
+
+ //Append items to media div
+ mediaDiv.appendChild(mediaTitle);
+
+ //Append media div to media container
+ mediaContainer.appendChild(mediaDiv);
+ }
+
+ //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 addURLPopup{
+ constructor(event, playlist, client, doc){
+ //Set Client
+ this.client = client;
+
+ //Set playlist
+ this.playlist = playlist
+
+ //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);
+ }
+
+ asyncConstructor(){
+ this.urlPrompt = this.popup.contentDiv.querySelector('#playlist-add-media-popup-prompt');
+ this.addButton = this.popup.contentDiv.querySelector('#playlist-add-media-popup-button');
+
+ this.setupInput();
+ }
+
+ setupInput(){
+ //Setup input
+ this.addButton.addEventListener('click', this.addToPlaylist.bind(this));
+ this.popup.popupDiv.addEventListener('keydown', this.addToPlaylist.bind(this));
+ }
+
+ addToPlaylist(event){
+ //If we clicked or hit enter
+ if(event.key == null || event.key == "Enter"){
+
+ //Tell the server to create a new channel playlist
+ this.client.socket.emit('addToChannelPlaylist', {
+ playlist: this.playlist,
+ url: this.urlPrompt.value
+ });
+
+ //Close the popup
+ this.popup.closePopup();
+ }
+ }
+}
+
+class defaultTitlesPopup{
+ constructor(event, playlist, titles, client, doc){
+ //Set Client
+ this.client = client;
+
+ //Set playlist
+ this.playlist = playlist
+
+ //Set title string
+ this.titles = titles.join('\n');
+
+ //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);
+ }
+
+ asyncConstructor(){
+ this.urlPrompt = this.popup.contentDiv.querySelector('#playlist-add-media-popup-prompt');
+ this.addButton = this.popup.contentDiv.querySelector('#playlist-add-media-popup-button');
+
+ this.setupInput();
+ }
+
+ setupInput(){
+ //Setup input
+ this.addButton.addEventListener('click', this.addToPlaylist.bind(this));
+ this.popup.popupDiv.addEventListener('keydown', this.addToPlaylist.bind(this));
+ }
+
+ addToPlaylist(event){
+ //If we clicked or hit enter
+ if(event.key == null || event.key == "Enter"){
+
+ //Tell the server to create a new channel playlist
+ this.client.socket.emit('addToChannelPlaylist', {
+ playlist: this.playlist,
+ url: this.urlPrompt.value
+ });
+
+ //Close the popup
+ this.popup.closePopup();
+ }
+ }
+}
diff --git a/www/js/utils.js b/www/js/utils.js
index 63c1ac0..3b70a2d 100644
--- a/www/js/utils.js
+++ b/www/js/utils.js
@@ -360,6 +360,7 @@ class canopyUXUtils{
this.content = content;
this.ajaxPopup = ajaxPopup;
this.cb = cb;
+ console.log(doc);
this.doc = doc;
//define popup nodes
this.createPopup();