Started work on playlist management UI
This commit is contained in:
parent
65b5ae9371
commit
1723e8ebd0
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
28
src/views/partial/popup/newPlaylist.ejs
Normal file
28
src/views/partial/popup/newPlaylist.ejs
Normal 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>
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue