Started work on playlist management UI
This commit is contained in:
parent
65b5ae9371
commit
1723e8ebd0
9 changed files with 586 additions and 104 deletions
|
|
@ -32,106 +32,196 @@ module.exports = class{
|
|||
}
|
||||
|
||||
defineListeners(socket){
|
||||
//socket.on("queue", (data) => {this.queueURL(socket, data)});
|
||||
socket.on("getChannelPlaylists", () => {this.getChannelPlaylists(socket)});
|
||||
socket.on("createChannelPlaylist", (data) => {this.createChannelPlaylist(socket, data)});
|
||||
socket.on("deleteChannelPlaylist", (data) => {this.deleteChannelPlaylist(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)});
|
||||
}
|
||||
|
||||
//--- USER-FACING PLAYLIST FUNCTIONS ---
|
||||
async getChannelPlaylists(socket, chanDB){
|
||||
//if we wherent handed a channel document
|
||||
if(chanDB == null){
|
||||
//Pull it based on channel name
|
||||
chanDB = await channelModel.findOne({name: this.channel.name});
|
||||
}
|
||||
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});
|
||||
}
|
||||
|
||||
//Return playlists
|
||||
socket.emit('chanPlaylists', chanDB.getPlaylists());
|
||||
//Return playlists
|
||||
socket.emit('chanPlaylists', chanDB.getPlaylists());
|
||||
}catch(err){
|
||||
return loggerUtils.socketExceptionHandler(socket, err);
|
||||
}
|
||||
}
|
||||
|
||||
async createChannelPlaylist(socket, data, chanDB){
|
||||
//if we wherent handed a channel document
|
||||
if(chanDB == null){
|
||||
//Pull it based on channel name
|
||||
chanDB = await channelModel.findOne({name: this.channel.name});
|
||||
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 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){
|
||||
//if we wherent handed a channel document
|
||||
if(chanDB == null){
|
||||
//Pull it based on channel name
|
||||
chanDB = await channelModel.findOne({name: this.channel.name});
|
||||
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});
|
||||
}
|
||||
|
||||
//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){
|
||||
//if we wherent handed a channel document
|
||||
if(chanDB == null){
|
||||
//Pull it based on channel name
|
||||
chanDB = await channelModel.findOne({name: this.channel.name});
|
||||
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});
|
||||
}
|
||||
|
||||
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){
|
||||
//if we wherent handed a channel document
|
||||
if(chanDB == null){
|
||||
//Pull it based on channel name
|
||||
chanDB = await channelModel.findOne({name: this.channel.name});
|
||||
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});
|
||||
}
|
||||
|
||||
//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
|
||||
let start = this.channel.queue.getStart(data.start);
|
||||
async renameChannelPlaylist(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});
|
||||
}
|
||||
|
||||
//Grab playlist from the DB
|
||||
let playlist = chanDB.getPlaylistByName(data.playlist);
|
||||
//If the title is too long
|
||||
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
|
||||
const mediaList = [];
|
||||
//Escape/trim the playlist name
|
||||
const name = validator.escape(validator.trim(data.name));
|
||||
|
||||
//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();
|
||||
//Find playlist
|
||||
let playlist = chanDB.getPlaylistByName(data.playlist);
|
||||
|
||||
//Set media title from default titles
|
||||
mediaObj.title = playlist.defaultTitles[Math.floor(Math.random() * playlist.defaultTitles.length)];
|
||||
//Change playlist name
|
||||
chanDB.media.playlists[playlist.listIndex].name = name;
|
||||
|
||||
//Push rehydrated item on to the mediaList
|
||||
mediaList.push(mediaObj);
|
||||
//Save channel document
|
||||
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(chanDB == null && !volatile){
|
||||
if(chanDB != null && !volatile){
|
||||
try{
|
||||
//Save the database
|
||||
chanDB.save();
|
||||
|
|
|
|||
|
|
@ -559,10 +559,7 @@ channelSchema.methods.getPlaylists = function(){
|
|||
//this.media.playlists.forEach((playlist) => {
|
||||
for(let playlist of this.media.playlists){
|
||||
//Push an object with select information from the emote to the emote list
|
||||
playlists.push({
|
||||
name: playlist.name,
|
||||
count: playlist.media.length
|
||||
});
|
||||
playlists.push(playlist.dehydrate());
|
||||
}
|
||||
|
||||
//return the emote list
|
||||
|
|
@ -584,11 +581,13 @@ channelSchema.methods.getPlaylistByName = function(name){
|
|||
let foundPlaylist = null;
|
||||
|
||||
//Crawl through active playlists
|
||||
this.playlistCrawl((playlist) => {
|
||||
this.playlistCrawl((playlist, listIndex) => {
|
||||
//If we found a match based on name
|
||||
if(playlist.name == name){
|
||||
//Keep it
|
||||
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){
|
||||
//Create null value to hold our found playlist
|
||||
let foundIndex = null;
|
||||
|
||||
//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;
|
||||
}
|
||||
});
|
||||
//Find the playlist
|
||||
let playlist = this.getPlaylistByName(name);
|
||||
|
||||
//splice out the given playlist
|
||||
this.media.playlists.splice(foundIndex, 1);
|
||||
this.media.playlists.splice(playlist.listIndex, 1);
|
||||
|
||||
//save the channel document
|
||||
await this.save();
|
||||
}
|
||||
|
||||
channelSchema.methods.addToPlaylist = async function(name, media){
|
||||
//Create variable to hold found index
|
||||
let foundIndex = null
|
||||
|
||||
//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
|
||||
}
|
||||
});
|
||||
//Find the playlist
|
||||
let playlist = this.getPlaylistByName(name);
|
||||
|
||||
//Set media status schema discriminator
|
||||
media.status = 'saved';
|
||||
|
||||
//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
|
||||
await this.save();
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ const playlistSchema = new mongoose.Schema({
|
|||
name: {
|
||||
type: mongoose.SchemaTypes.String,
|
||||
required: true,
|
||||
unique: true
|
||||
},
|
||||
media: [playlistMediaSchema],
|
||||
defaultTitles:[{
|
||||
|
|
@ -33,8 +34,26 @@ const playlistSchema = new mongoose.Schema({
|
|||
}]
|
||||
});
|
||||
|
||||
playlistSchema.methods.test = function(){
|
||||
console.log(this.name);
|
||||
//methods
|
||||
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;
|
||||
|
|
@ -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>
|
||||
</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 id="queue-control-offset"></div>
|
||||
<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>
|
||||
Loading…
Add table
Add a link
Reference in a new issue