Browse Source

Worked on queue and added toasts.

BuildTools 8 years ago
parent
commit
5d954b099e

+ 3 - 2
backend/app.js

@@ -5,7 +5,7 @@ const path = require('path'),
       fs   = require('fs'),
       os   = require('os');
 
-process.env.NODE_CONFIG_DIR = `${process.cwd()}/config`;
+process.env.NODE_CONFIG_DIR = `${process.cwd()}/backend/config`;
 
 // npm modules
 const express          = require('express'),
@@ -51,7 +51,8 @@ function setupExpress() {
 	global.db = {
 		user: require('./schemas/user')(mongoose),
 		station: require('./schemas/station')(mongoose),
-		song: require('./schemas/song')(mongoose)
+		song: require('./schemas/song')(mongoose),
+		queueSong: require('./schemas/queueSong')(mongoose)
 	};
 
 	const mongoStore = new MongoStore({'mongooseConnection': MongoDB});

+ 101 - 0
backend/logic/coreHandler.js

@@ -172,6 +172,107 @@ module.exports = {
 		}
 	},
 
+	'/youtube/getVideos/:query': (query, cb) => {
+		cb({
+			type: "query",
+			items: [
+				{
+					id: "39fk3490krf9",
+					title: "Test Title",
+					channel: "Test Channel",
+					duration: 200,
+					image: "https://i.ytimg.com/vi/lwg5yAuanPg/hqdefault.jpg?custom=true&w=196&h=110&stc=true&jpg444=true&jpgq=90&sp=68&sigh=DWOZl_nkv78qzj8WHPY1-53iQfA"
+				},
+				{
+					id: "49iug05it",
+					title: "Test Title 222",
+					channel: "Test Channel 222",
+					duration: 100,
+					image: "https://i.ytimg.com/vi/QJwIsBoe3Lg/hqdefault.jpg?custom=true&w=196&h=110&stc=true&jpg444=true&jpgq=90&sp=68&sigh=8L20nlTyPf7xuIB8DTeBQFWW2Xw"
+				}
+			]
+		});
+	},
+
+	'/songs/queue/addSongs/:songs': (songs, user, cb) => {
+		if (user !== null && user !== undefined && user.logged_in) {
+			if (Array.isArray(songs)) {
+				if (songs.length > 0) {
+					let failed = 0;
+					let success = 0;
+					songs.forEach(function (song) {
+						if (typeof song === "object" && song !== null) {
+							let obj = {};
+							obj.title = song.title;
+							obj._id = song.id;
+							obj.artists = [];
+							obj.image = "test";
+							obj.duration = 0;
+							obj.genres = ["edm"];
+							//TODO Get data from Wikipedia and Spotify
+							obj.requestedBy = user._id;
+							console.log(user._id);
+							console.log(user);
+							obj.requestedAt = Date.now();
+							let queueSong = new global.db.queueSong(obj);
+							queueSong.save(function(err) {
+								console.log(err);
+								if (err) failed++;
+								else success++;
+							});
+						} else {
+							failed++;
+						}
+					});
+					cb({success, failed});
+				} else {
+					cb({err: "No songs supplied."});
+				}
+			} else {
+				cb({err: "Not supplied an array."});
+			}
+		} else {
+			cb({err: "Not logged in."});
+		}
+	},
+
+	'/songs/queue/getSongs': (user, cb) => {
+		if (user !== null && user !== undefined && user.logged_in) {
+			global.db.queueSong.find({}, function(err, songs) {
+				if (err) throw err;
+				else cb({songs: songs});
+			});
+		} else {
+			cb({err: "Not logged in."});
+		}
+	},
+
+	'/songs/queue/updateSong/:id': (user, id, object, cb) => {
+		if (user !== null && user !== undefined && user.logged_in) {
+			global.db.queueSong.findOne({_id: id}, function(err, song) {
+				if (err) throw err;
+				else {
+					if (song !== undefined && song !== null) {
+						if (typeof object === "object" && object !== null) {
+							delete object.requestedBy;
+							delete object.requestedAt;
+							global.db.queueSong.update({_id: id}, {$set: object}, function(err, song) {
+								if (err) throw err;
+								cb({success: true});
+							});
+						} else {
+							cb({err: "Invalid data."});
+						}
+					} else {
+						cb({err: "Song not found."});
+					}
+				}
+			});
+		} else {
+			cb({err: "Not logged in."});
+		}
+	},
+
 	/*'/stations/search/:query': (query, cb) => {
 
 		const params = [

+ 27 - 1
backend/logic/socketHandler.js

@@ -4,6 +4,8 @@ module.exports = (core, io) => {
 
 	io.on('connection', socket => {
 		console.log("User has connected");
+		let _user = socket.request.user;
+
 		socket.on('disconnect', () => {
 			console.log('User has disconnected');
 		});
@@ -26,6 +28,30 @@ module.exports = (core, io) => {
 			});
 		});
 
+		socket.on('/youtube/getVideos/:query', (query, cb) => {
+			core['/youtube/getVideos/:query'](query, result => {
+				cb(result);
+			});
+		});
+
+		socket.on('/songs/queue/addSongs/:songs', (songs, cb) => {
+			core['/songs/queue/addSongs/:songs'](songs, _user, result => {
+				cb(result);
+			});
+		});
+
+		socket.on('/songs/queue/getSongs', (cb) => {
+			core['/songs/queue/getSongs'](_user, result => {
+				cb(result);
+			});
+		});
+
+		socket.on('/songs/queue/updateSong/:id', (id, object, cb) => {
+			core['/songs/queue/updateSong/:id'](_user, id, object, result => {
+				cb(result);
+			});
+		});
+
 		/*socket.on('/stations/search/:query', (query, cb) => {
 			core['/stations/search/:query'](query, result => {
 				cb(result);
@@ -33,6 +59,6 @@ module.exports = (core, io) => {
 		});*/
 
 		// this lets the client socket know that they can start making request
-		//socket.emit('ready', socket.request.user.logged_in);
+		//socket.emit('ready', user.logged_in);
 	});
 };

+ 2 - 4
backend/schemas/queueSong.js

@@ -8,13 +8,11 @@ module.exports = mongoose => {
 		artists: [{ type: String, min: 1 }],
 		duration: { type: Number, required: true },
 		skipDuration: { type: Number, required: true, default: 0 },
-		image: { type: String, required: true },
-		likes: { type: Number, required: true },
-		dislikes: { type: Number, required: true },
+		image: { type: String, required: true, default: "" },
 		genres: [{ type: String }],
 		requestedBy: { type: String, required: true },
 		requestedAt: { type: Date, required: true },
 	});
 
-	return queueSongSchema;
+	return mongoose.model('queueSong', queueSongSchema);
 };

+ 73 - 1
frontend/App.vue

@@ -1,4 +1,7 @@
 <template>
+	<div id="toasts">
+		<span v-for="toast in toasts" v-bind:class="toast.class">{{toast.text}}</span>
+	</div>
 	<div>
 		<router-view></router-view>
 	</div>
@@ -48,7 +51,9 @@
 							{ id: "w19hu791iiub6wmjf9a4i", thumbnail: "http://edmsauce.wpengine.netdna-cdn.com/wp-content/uploads/2012/12/Deadmau5-album-title-goes-here.jpg", name: "EDM", description: "Deadmau5 - There Might Be Coffee", users: 13 }
 						]
 					}
-				]
+				],
+				toastCount: 0,
+				toasts: []
 			}
 		},
 		methods: {
@@ -62,6 +67,36 @@
 						location.reload();
 					}
 				});
+			},
+			toast(text, duration) {
+				let local = this;
+				let id = local.toastCount++;
+
+				this.toasts.push({id: id, text: text, class: "toast toast-add"});
+				if (duration > 250) {
+					setTimeout(function() {
+						local.toasts = local.toasts.map(function(toast) {
+							if (toast.id === id) {
+								toast.class = "toast";
+							}
+							return toast;
+						});
+					}, 250);
+				}
+
+				setTimeout(function() {
+					local.toasts = local.toasts.map(function(toast) {
+						if (toast.id === id) {
+							toast.class = "toast toast-remove";
+						}
+						return toast;
+					});
+					setTimeout(function() {
+						local.toasts = local.toasts.filter(function(toast) {
+							return toast.id !== id;
+						});
+					}, 250);
+				}, duration);
 			}
 		},
 		ready: function () {
@@ -131,3 +166,40 @@
 		}
 	}
 </script>
+
+<style lang="sass" scoped>
+	#toasts {
+		position: fixed;
+		z-index: 100000;
+		right: 5%;
+		top: 10%;
+		max-width: 90%;
+
+		.toast {
+			width: 100%;
+			height: auto;
+			padding: 10px 20px;
+			border-radius: 3px;
+			color: white;
+			background-color: #424242;
+			display: -webkit-flex;
+			display: -ms-flexbox;
+			display: flex;
+			margin-bottom: 10px;
+			font-size: 1.18em;
+			font-weight: 400;
+			box-shadow: 0 2px 5px 0 rgba(0,0,0,0.16), 0 2px 10px 0 rgba(0,0,0,0.12);
+			transition: all 0.25s ease;
+		}
+
+		.toast-remove {
+			opacity: 0;
+			margin-top: -50px;
+		}
+
+		.toast-add {
+			opacity: 0;
+			margin-top: 50px;
+		}
+	}
+</style>

+ 1 - 0
frontend/components/MainHeader.vue

@@ -12,6 +12,7 @@
 			</div>
 			<div class="collapse navbar-collapse" id="main-navbar">
 				<ul class="nav navbar-nav navbar-right">
+					<li><a v-link="{ path: '/admin/queue' }">Admin Queue</a></li>
 					<li><a href="#">The Project</a></li>
 					<li><a href="#">Donate</a></li>
 					<li><a href="#" @click="$parent.logout()">Logout</a></li>

+ 1 - 1
frontend/components/StationHeader.vue

@@ -12,7 +12,7 @@
 			<div class="collapse navbar-collapse" id="station-navbar">
 				<ul class="nav navbar-nav">
 					<li class="pull-left"><a href="#" v-link="{ path: '/' }"><i class="material-icons left">home</i></a></li>
-					<li class="pull-left"><a href="#"><i class="material-icons left">playlist_add</i></a></li>
+					<li class="pull-left"><a href="#" data-toggle="modal" data-target="#queue"><i class="material-icons left">playlist_add</i></a></li>
 					<li class="pull-left"><a href="#"><i class="material-icons left">flag</i></a></li>
 					<li class="pull-left"><a href="#"><i class="material-icons left">skip_next</i></a></li>
 					<li class="pull-center"><a href="#">Station Name</a></li>

+ 117 - 0
frontend/components/pages/AdminQueue.vue

@@ -0,0 +1,117 @@
+<template>
+	<div class="app">
+		<main-header></main-header>
+		<table>
+			<thead>
+				<tr>
+					<td>Title</td>
+					<td>Artists</td>
+					<td>Genre's</td>
+					<td>Controls</td>
+				</tr>
+			</thead>
+			<tbody>
+				<tr v-for="song in songs">
+					<td>{{song.title}}</td>
+					<td>{{song.artists}}</td>
+					<td>{{song.genres}}</td>
+					<td><button @click="reviewSong(song._id)">Review</button></td>
+				</tr>
+			</tbody>
+		</table>
+		<main-footer></main-footer>
+		<div class="modal fade" id="reviewModal" tabindex="-1" role="dialog" aria-labelledby="review-modal">
+			<div class="modal-dialog modal-large" role="document">
+				<div class="modal-content">
+					<div class="modal-header">
+						<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
+						<h5 class="modal-title">Review</h5>
+					</div>
+					<div class="modal-body">
+						<label for="reviewId">ID</label>
+						<input type="text" v-bind:value="reviewSongObject._id" id="reviewId"/><br>
+						<label for="reviewTitle">Title</label>
+						<input type="text" v-bind:value="reviewSongObject.title" id="reviewTitle"/><br>
+						<label for="reviewArtists">Artists</label>
+						<input type="text" v-bind:value="reviewSongObject.artists" id="reviewArtists"/><br>
+						<label for="reviewGenres">Genres</label>
+						<input type="text" v-bind:value="reviewSongObject.genres" id="reviewGenres"/><br>
+						<label for="reviewDuration">Duration</label>
+						<input type="number" v-bind:value="reviewSongObject.duration" id="reviewDuration"/><br>
+						<label for="reviewSkipDuration">Skip Duration</label>
+						<input type="number" v-bind:value="reviewSongObject.skipDuration" id="reviewSkipDuration"/><br>
+						<label for="reviewImage">Image</label>
+						<input type="text" v-bind:value="reviewSongObject.image" id="reviewImage"/>
+					</div>
+					<div class="modal-footer">
+						<button type="button" class="btn btn-primary left" @click="saveQueueSongChanges()">Save</button>
+					</div>
+				</div>
+			</div>
+		</div>
+	</div>
+</template>
+
+<script>
+	import MainHeader from '../MainHeader.vue'
+	import MainFooter from '../MainFooter.vue'
+
+	export default {
+		components: { MainHeader, MainFooter },
+		data() {
+			return {
+				songs: [],
+				reviewSongObject: {},
+				reviewSongId: ""
+			}
+		},
+		methods: {
+			reviewSong: function(id) {
+				let local = this;
+				local.reviewSongObject = {};
+				local.reviewSongId = "";
+				local.songs.forEach(function(song) {
+					if (song._id === id) {
+						for (var prop in song) {
+							local.reviewSongObject[prop] = song[prop];
+						}
+						local.reviewSongId = id;
+						$('#reviewModal').modal('show');
+					}
+				});
+			},
+			saveQueueSongChanges: function() {
+				let local = this;
+				let songId = local.reviewSongId;
+				let songObject = {};
+				songObject._id = $("#reviewId").val();
+				songObject.title = $("#reviewTitle").val();
+				songObject.artists = $("#reviewArtists").val();
+				songObject.genres = $("#reviewGenres").val();
+				songObject.duration = $("#reviewDuration").val();
+				songObject.skipDuration = $("#reviewSkipDuration").val();
+				songObject.image = $("#reviewImage").val();
+				if (typeof songObject.artists === "string") {
+					songObject.artists = songObject.artists.split(", ");
+				}
+				if (typeof songObject.genres === "string") {
+					songObject.genres = songObject.genres.split(", ");
+				}
+				local.socket.emit("/songs/queue/updateSong/:id", songId, songObject, function(data) {
+					console.log(data);
+				});
+			}
+		},
+		ready: function() {
+			let local = this;
+			local.socket = this.$parent.socket;
+			local.socket.emit("/songs/queue/getSongs", function(data) {
+				local.songs = data.songs;
+			});
+		}
+	}
+</script>
+
+<style lang="sass">
+
+</style>

+ 2 - 6
frontend/components/pages/Home.vue

@@ -1,9 +1,6 @@
 <template>
 	<div class="app">
 		<main-header></main-header>
-		<toast>
-			Test
-		</toast>
 		<div class="modal fade" id="register" tabindex="-1" role="dialog" aria-labelledby="register-modal">
 			<div class="modal-dialog" role="document">
 				<div class="modal-content">
@@ -15,7 +12,7 @@
 						<input class="form-control" type="text" placeholder="Email..." v-model="$parent.register.email"/>
 						<input class="form-control" type="text" placeholder="Username..." v-model="$parent.register.username"/>
 						<input class="form-control" type="password" placeholder="Password..." v-model="$parent.register.password"/>
-						<div class="g-recaptcha" data-sitekey="6LdNCQcUAAAAANj_w5leQSrxnAmDp2ioh4alkUHg"></div>
+						<div class="g-recaptcha" data-sitekey="6Lfa-wYUAAAAANY6iVvWNEXohC38l1cZqHRole9T"></div>
 					</div>
 					<div class="modal-footer">
 						<button type="button" class="btn btn-primary" data-dismiss="modal" @click="this.$dispatch('register');">Submit</button>
@@ -67,10 +64,9 @@
 <script>
 	import MainHeader from '../MainHeader.vue'
 	import MainFooter from '../MainFooter.vue'
-	import Toast from 'vue-roaster/src/Toast.vue'
 
 	export default {
-		components: { MainHeader, MainFooter, Toast }
+		components: { MainHeader, MainFooter }
 	}
 </script>
 

+ 127 - 3
frontend/components/pages/Station.vue

@@ -39,6 +39,40 @@
 		</div>
 	</div>
 	<main-footer></main-footer>
+	<div class="modal fade" id="queue" tabindex="-1" role="dialog" aria-labelledby="queue-modal">
+		<div class="modal-dialog modal-large" role="document">
+			<div class="modal-content">
+				<div class="modal-header">
+					<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
+					<h5 class="modal-title">Add to Musare</h5>
+				</div>
+				<div class="modal-body">
+					<input class="form-control" type="text" placeholder="YouTube Query / Video ID / Video link / Playlist link" v-model="queueQuery"/>
+					<button type="button" class="btn btn-primary" @click="submitQueueQuery()">Search</button>
+					<button type="button" class="btn btn-error" @click="clearQueueQuery()" v-if="queueQueryActive">Clear List</button>
+					<div v-if="queueQueryActive">
+						<h2>Queue Results</h2>
+						<div v-for="item in queueQueryResults">
+							<h5>{{item.title}}</h5>
+							<button @click='addItemToItems(item.id)'>Add</button>
+							<br>
+						</div>
+					</div>
+					<hr>
+					<div class="row">
+						<h2>Items to add</h2>
+						<div v-for="item in queueItems">
+							<h5>{{item.title}}</h5>
+							<br>
+						</div>
+					</div>
+				</div>
+				<div class="modal-footer">
+					<button type="button" class="btn btn-primary left" data-dismiss="modal" @click="addItemsToQueue()">Add items to queue</button>
+				</div>
+			</div>
+		</div>
+	</div>
 </template>
 
 <script>
@@ -60,7 +94,11 @@
 				image: "",
 				likes: 0,
 				dislikes: 0,
-				interval: 0
+				interval: 0,
+				queueQuery: "",
+				queueQueryActive: false,
+				queueQueryResults: [],
+				queueItems: []
 			}
 		},
 		methods: {
@@ -197,6 +235,88 @@
 			toggleDislike: function() {
 				let local = this;
 				local.stationSocket.emit("toggleDislike");//TODO Add code here to see if this was a success or not
+			},
+			addItemToItems: function(id) {
+				let local = this;
+				let ids = local.queueItems.map(function(item) {
+					return item.id;
+				});
+				let item;
+				local.queueQueryResults.forEach(function(result) {
+					if (result.id === id) {
+						console.log(result);
+						item = result;
+					}
+				});
+				if (ids.indexOf(id) === -1) {
+					console.log(item, 222);
+					local.queueItems.push(item);
+					local.queueQuery = "";
+					local.queueQueryActive = false;
+					local.queueQueryResults = [];
+				} else {
+					//TODO Error
+				}
+			},
+			addItemsToQueue: function() {
+				let local = this;
+				let items = local.queueItems;
+				local.socket.emit("/songs/queue/addSongs/:songs", items, function(data) {
+					console.log(data);
+					if (!data.err) {
+						local.queueItems = [];
+						$('#queue').modal('hide');
+					}
+				});
+			},
+			submitQueueQuery: function() {
+				let local = this;
+				let query = local.queueQuery;
+				local.socket.emit("/youtube/getVideos/:query", query, function(data) {
+					if (!data.err) {
+						/*queueQueryActive:
+						 queueQueryResults:*/
+						if (data.type === "playlist") {
+							let added = 0;
+							let duplicate = 0;
+							let items = [];
+							let ids = local.queueItems.map(function(item) {
+								return item.id;
+							});
+
+							data.items.forEach(function(item) {
+								if (ids.indexOf(item.id) === -1) {
+									items.push(item);
+									added++;
+								} else {
+									duplicate++;
+								}
+							});
+
+							//TODO Give result
+							local.queueItems = local.queueItems.concat(items);
+						} else if (data.type === "video") {
+							let ids = local.queueItems.map(function(item) {
+								return item.id;
+							});
+
+							if (data.item !== undefined) {
+								if (ids.indexOf(data.item.id)) {
+									local.queueItems.push(data.item);
+								}
+							}
+
+							//TODO Give result
+						} else {
+							local.queueQueryResults = [];
+							data.items.forEach(function(item) {
+								local.queueQueryResults.push(item);
+							});
+							//TODO Give result
+							local.queueQueryActive = true;
+						}
+					}
+				});
 			}
 		},
 		ready: function() {
@@ -205,7 +325,7 @@
 				local.youtubeReady();
 			};
 
-			let socket = this.$parent.socket;
+			local.socket = this.$parent.socket;
 			local.stationSocket = io.connect('http://dev.musare.com/edm');
 			local.stationSocket.on("skippedSong", function(currentSong) {
 				console.log("SKIPPED SONG");
@@ -229,7 +349,7 @@
 			$("#volumeSlider").val(volume);
 
 			// TODO: Remove this
-			socket.emit("/stations/join/:id", "edm", function(data) {
+			local.socket.emit("/stations/join/:id", "edm", function(data) {
 				local.currentSong = data.data.currentSong;
 				local.paused = data.data.paused;
 				local.timePaused = data.data.timePaused;
@@ -246,6 +366,10 @@
 </script>
 
 <style lang="sass">
+	.modal-large {
+		width: 75%;
+	}
+
 	.station {
 		flex: 1 0 auto;
 		padding-top: 4.5vw;

+ 4 - 0
frontend/main.js

@@ -3,6 +3,7 @@ import VueRouter from 'vue-router';
 import App from './App.vue';
 import Home from './components/pages/Home.vue';
 import Station from './components/pages/Station.vue';
+import AdminQueue from './components/pages/AdminQueue.vue';
 
 Vue.use(VueRouter);
 let router = new VueRouter({ history: true });
@@ -13,6 +14,9 @@ router.map({
 	},
 	'/station': {
 		component: Station
+	},
+	'/admin/queue': {
+		component: AdminQueue
 	}
 });