Browse Source

Merge pull request #17 from Musare/staging

Beta Release Day 10
Vos 8 years ago
parent
commit
13c9bf0024

+ 3 - 3
backend/logic/actions/hooks/ownerRequired.js

@@ -11,16 +11,16 @@ module.exports = function(next) {
 			if (err || !session || !session.userId) return cb({ status: 'failure', message: 'Login required.' });
 			db.models.user.findOne({_id: session.userId}, (err, user) => {
 				if (err || !user) return cb({ status: 'failure', message: 'Login required.' });
-				if (user.role === 'admin') func();
+				if (user.role === 'admin') pushArgs();
 				else {
 					stations.getStation(stationId, (err, station) => {
 						if (err || !station) return cb({ status: 'failure', message: 'Something went wrong when getting the station.' });
-						else if (station.type === 'community' && station.owner === session.userId) func();
+						else if (station.type === 'community' && station.owner === session.userId) pushArgs();
 						else return cb({ status: 'failure', message: 'Invalid permissions.' });
 					});
 				}
 
-				function func() {
+				function pushArgs() {
 					args.push(session.userId);
 					next.apply(null, args);
 				}

+ 5 - 1
backend/logic/actions/songs.js

@@ -96,7 +96,7 @@ module.exports = {
 						console.error(err);
 						cb({ status: 'failure', message: 'Something went wrong while adding the song to the queue.' });
 					} else {
-						cache.pub('song.added', songId);
+						cache.pub('song.added', song._id);
 						cb({ status: 'success', message: 'Song has been moved from Queue' });
 					}
 				});
@@ -106,6 +106,7 @@ module.exports = {
 	}),
 
 	like: hooks.loginRequired((session, songId, cb, userId) => {
+		return cb({ status: 'failure', message: 'Ratings are currently disabled.' });
 		db.models.user.findOne({ _id: userId }, (err, user) => {
 			if (user.liked.indexOf(songId) !== -1) return cb({ status: 'failure', message: 'You have already liked this song.' });
 			let dislikes = 0;
@@ -128,6 +129,7 @@ module.exports = {
 	}),
 
 	dislike: hooks.loginRequired((session, songId, cb, userId) => {
+		return cb({ status: 'failure', message: 'Ratings are currently disabled.' });
 		db.models.user.findOne({_id: userId}, (err, user) => {
 			if (user.disliked.indexOf(songId) !== -1) return cb({ status: 'failure', message: 'You have already disliked this song.' });
 			let likes = 0;
@@ -150,6 +152,7 @@ module.exports = {
 	}),
 
 	undislike: hooks.loginRequired((session, songId, cb, userId) => {
+		return cb({ status: 'failure', message: 'Ratings are currently disabled.' });
 		db.models.user.findOne({_id: userId}, (err, user) => {
 			if (user.disliked.indexOf(songId) === -1) return cb({ status: 'failure', message: 'You have not disliked this song.' });
 			db.models.song.update({_id: songId}, {$inc: {dislikes: -1}}, (err) => {
@@ -170,6 +173,7 @@ module.exports = {
 	}),
 
 	unlike: hooks.loginRequired((session, songId, cb, userId) => {
+		return cb({ status: 'failure', message: 'Ratings are currently disabled.' });
 		db.models.user.findOne({_id: userId}, (err, user) => {
 			if (user.liked.indexOf(songId) === -1) return cb({ status: 'failure', message: 'You have not liked this song.' });
 			db.models.song.update({_id: songId}, {$inc: {likes: -1}}, (err) => {

+ 12 - 15
backend/logic/actions/stations.js

@@ -132,17 +132,15 @@ module.exports = {
 	},
 
 	getPlaylist: (session, stationId, cb) => {
-		let playlist = [];
-
 		stations.getStation(stationId, (err, station) => {
-			for (let s = 1; s < station.playlist.length; s++) {
-				songs.getSong(station.playlist[s], (err, song) => {
-					playlist.push(song);
-				});
-			}
+			if (err) return cb({ status: 'failure', message: 'Something went wrong when getting the station.' });
+			if (station.type === 'official') {
+				cache.hget("officialPlaylists", stationId, (err, playlist) => {
+					if (err) return cb({ status: 'failure', message: 'Something went wrong when getting the playlist.' });
+					cb({ status: 'success', data: playlist.songs })
+				})
+			} else cb({ status: 'failure', message: 'This is not an official station.' })
 		});
-
-		cb({ status: 'success', data: playlist })
 	},
 
 	/**
@@ -161,21 +159,20 @@ module.exports = {
 
 			if (station) {
 
-				if (station.privacy !== 'private') {
-					func();
-				} else {
+				if (station.privacy !== 'private') joinStation();
+				else {
 					// TODO If community, check if on whitelist
 					if (!session.userId) return cb({ status: 'error', message: 'An error occurred while joining the station1' });
 					db.models.user.findOne({_id: session.userId}, (err, user) => {
 						if (err || !user) return cb({ status: 'error', message: 'An error occurred while joining the station2' });
-						if (user.role === 'admin') return func();
+						if (user.role === 'admin') return joinStation();
 						if (station.type === 'official') return cb({ status: 'error', message: 'An error occurred while joining the station3' });
-						if (station.owner === session.userId) return func();
+						if (station.owner === session.userId) return joinStation();
 						return cb({ status: 'error', message: 'An error occurred while joining the station4' });
 					});
 				}
 
-				function func() {
+				function joinStation() {
 					utils.socketJoinRoom(session.socketId, `station.${stationId}`);
 					if (station.currentSong) {
 						utils.socketJoinSongRoom(session.socketId, `song.${station.currentSong._id}`);

+ 13 - 7
backend/logic/cache/index.js

@@ -16,6 +16,7 @@ const lib = {
 		session: require('./schemas/session'),
 		station: require('./schemas/station'),
 		playlist: require('./schemas/playlist'),
+		officialPlaylist: require('./schemas/officialPlaylist'),
 		song: require('./schemas/song')
 	},
 
@@ -29,7 +30,10 @@ const lib = {
 		lib.url = url;
 
 		lib.client = redis.createClient({ url: lib.url });
-		lib.client.on('error', (err) => console.error(err));
+		lib.client.on('error', (err) => {
+			console.error(err);
+			process.exit();
+		});
 
 		initialized = true;
 		callbacks.forEach((callback) => {
@@ -143,17 +147,19 @@ const lib = {
 	 * @param {Boolean} [parseJson=true] - parse the message as JSON
 	 */
 	sub: (channel, cb, parseJson = true) => {
-		if (initialized) {
-			func();
-		} else {
+		if (initialized) subToChannel();
+		else {
 			callbacks.push(() => {
-				func();
+				subToChannel();
 			});
 		}
-		function func() {
+		function subToChannel() {
 			if (subs[channel] === undefined) {
 				subs[channel] = { client: redis.createClient({ url: lib.url }), cbs: [] };
-				subs[channel].client.on('error', (err) => console.error(err));
+				subs[channel].client.on('error', (err) => {
+					console.error(err);
+					process.exit();
+				});
 				subs[channel].client.on('message', (channel, message) => {
 					if (parseJson) try { message = JSON.parse(message); } catch (e) {}
 					subs[channel].cbs.forEach((cb) => cb(message));

+ 8 - 0
backend/logic/cache/schemas/officialPlaylist.js

@@ -0,0 +1,8 @@
+'use strict';
+
+module.exports = (stationId, songs) => {
+	return {
+		stationId,
+		songs
+	}
+};

+ 4 - 1
backend/logic/db/index.js

@@ -12,7 +12,10 @@ let lib = {
 
 		lib.connection = mongoose.connect(url).connection;
 
-		lib.connection.on('error', err => console.error('Database error: ' + err.message));
+		lib.connection.on('error', err => {
+			console.error('Database error: ' + err.message)
+			process.exit();
+		});
 
 		lib.connection.once('open', _ => {
 

+ 4 - 1
backend/logic/notifications.js

@@ -19,7 +19,10 @@ const lib = {
 	init: (url, cb) => {
 		pub = redis.createClient({ url: url });
 		sub = redis.createClient({ url: url });
-		sub.on('error', (err) => console.error);
+		sub.on('error', (err) => {
+			console.error(err);
+			process.exit();
+		});
 		sub.on('pmessage', (pattern, channel, expiredKey) => {
 			subscriptions.forEach((sub) => {
 				if (sub.name !== expiredKey) return;

+ 73 - 5
backend/logic/stations.js

@@ -26,6 +26,14 @@ cache.sub('station.queueUpdate', (stationId) => {
 	});
 });
 
+cache.sub('station.newOfficialPlaylist', (stationId) => {
+	cache.hget("officialPlaylists", stationId, (err, playlistObj) => {
+		if (!err && playlistObj) {
+			utils.emitToRoom(`station.${stationId}`, "event:newOfficialPlaylist", playlistObj.songs);
+		}
+	})
+});
+
 module.exports = {
 
 	init: function(cb) {
@@ -116,6 +124,13 @@ module.exports = {
 				station.playlist.filter((songId) => {
 					if (songList.indexOf(songId) !== -1) playlist.push(songId);
 				});
+
+				_this.calculateOfficialPlaylistList(station._id, playlist, () => {
+					next(null, playlist);
+				});
+			},
+
+			(playlist, next) => {
 				db.models.station.update({_id: station._id}, {$set: {playlist: playlist}}, (err) => {
 					_this.updateStation(station._id, () => {
 						next(err, playlist);
@@ -130,6 +145,7 @@ module.exports = {
 
 	// Attempts to get the station from Redis. If it's not in Redis, get it from Mongo and add it to Redis.
 	getStation: function(stationId, cb) {
+		let _this = this;
 		async.waterfall([
 
 			(next) => {
@@ -143,6 +159,9 @@ module.exports = {
 
 			(station, next) => {
 				if (station) {
+					if (station.type === 'official') {
+						_this.calculateOfficialPlaylistList(station._id, station.playlist, ()=>{});
+					}
 					station = cache.schemas.station(station);
 					cache.hset('stations', stationId, station);
 					next(true, station);
@@ -155,7 +174,8 @@ module.exports = {
 		});
 	},
 
-	updateStation: (stationId, cb) => {
+	updateStation: function(stationId, cb) {
+		let _this = this;
 		async.waterfall([
 
 			(next) => {
@@ -176,6 +196,33 @@ module.exports = {
 		});
 	},
 
+	calculateOfficialPlaylistList: (stationId, songList, cb) => {
+		let lessInfoPlaylist = [];
+
+		function getSongInfo(index) {
+			if (songList.length > index) {
+				songs.getSong(songList[index], (err, song) => {
+					if (!err && song) {
+						let newSong = {
+							_id: song._id,
+							title: song.title,
+							artists: song.artists,
+							duration: song.duration
+						};
+						lessInfoPlaylist.push(newSong);
+					}
+					getSongInfo(index + 1);
+				})
+			} else {
+				cache.hset("officialPlaylists", stationId, cache.schemas.officialPlaylist(stationId, lessInfoPlaylist), () => {
+					cache.pub("station.newOfficialPlaylist", stationId);
+					cb();
+				});
+			}
+		}
+		getSongInfo(0);
+	},
+
 	skipStation: function(stationId) {
 		let _this = this;
 		return (cb) => {
@@ -188,7 +235,7 @@ module.exports = {
 						(next) => {
 							if (station.type === "official") {
 								if (station.playlist.length > 0) {
-									function func() {
+									function setCurrentSong() {
 										if (station.currentSongIndex < station.playlist.length - 1) {
 											songs.getSong(station.playlist[station.currentSongIndex + 1], (err, song) => {
 												if (!err) {
@@ -209,9 +256,9 @@ module.exports = {
 													$set.currentSongIndex = station.currentSongIndex + 1;
 													next(null, $set);
 												} else {
-													db.models.station.update({_id: station._id}, {$inc: {currentSongIndex: 1}}, (err) => {
+													db.models.station.update({ _id: station._id }, { $inc: { currentSongIndex: 1 } }, (err) => {
 														_this.updateStation(station._id, () => {
-															func();
+															setCurrentSong();
 														});
 													});
 												}
@@ -253,7 +300,7 @@ module.exports = {
 										}
 									}
 
-									func();
+									setCurrentSong();
 								} else {
 									_this.calculateSongForStation(station, (err, playlist) => {
 										if (!err && playlist.length === 0) {
@@ -356,12 +403,33 @@ module.exports = {
 							if (station.currentSong !== null && station.currentSong._id !== undefined) {
 								station.currentSong.skipVotes = 0;
 							}
+							//TODO Pub/Sub this
 							utils.emitToRoom(`station.${station._id}`, "event:songs.next", {
 								currentSong: station.currentSong,
 								startedAt: station.startedAt,
 								paused: station.paused,
 								timePaused: 0
 							});
+
+							if (station.privacy === 'public') utils.emitToRoom('home', "event:station.nextSong", station._id, station.currentSong);
+							else {
+								let sockets = utils.getRoomSockets('home');
+								for (let socketId in sockets) {
+									let socket = sockets[socketId];
+									let session = sockets[socketId].session;
+									if (session.sessionId) {
+										cache.hget('sessions', session.sessionId, (err, session) => {
+											if (!err && session) {
+												db.models.user.findOne({_id: session.userId}, (err, user) => {
+													if (user.role === 'admin') socket.emit("event:station.nextSong", station._id, station.currentSong);
+													else if (station.type === "community" && station.owner === session.userId) socket.emit("event:station.nextSong", station._id, station.currentSong);
+												});
+											}
+										});
+									}
+								}
+							}
+
 							if (station.currentSong !== null && station.currentSong._id !== undefined) {
 								utils.socketsJoinSongRoom(utils.getRoomSockets(`station.${station._id}`), `song.${station.currentSong._id}`);
 								if (!station.paused) {

+ 4 - 0
frontend/App.vue

@@ -138,6 +138,10 @@
 <style type='scss'>
 	#toast-container { z-index: 10000 !important; }
 
+	html {
+		overflow: auto !important;
+	}
+
 	.absolute-a {
 		width: 100%;
 		height: 100%;

+ 14 - 5
frontend/components/Admin/QueueSongs.vue

@@ -113,9 +113,15 @@
 				else if (type == 'artists') this.editing.song.artists.splice(index, 1);
 			},
 			edit: function (song, index) {
-				this.editing = { index, song };
-				this.video.player.loadVideoById(song._id);
-				this.isEditActive = true;
+				if (this.video.player) {
+					this.video.player.loadVideoById(song._id);
+					let songCopy = {};
+					for (let n in song) {
+						songCopy[n] = song[n];
+					}
+					this.editing = { index, song: songCopy };
+					this.isEditActive = true;
+				}
 			},
 			save: function (song) {
 				let _this = this;
@@ -175,10 +181,13 @@
 						if (volume > 0) _this.video.player.unMute();
 					},
 					'onStateChange': event => {
-						if (event.data == 1) {
+						if (event.data === 1) {
 							let youtubeDuration = _this.video.player.getDuration();
 							youtubeDuration -= _this.editing.song.skipDuration;
-							if (_this.editing.song.duration > youtubeDuration) this.stopVideo();
+							if (_this.editing.song.duration > youtubeDuration) {
+								this.video.player.stopVideo();
+								Toast.methods.addToast("Video can't play. Specified duration is bigger than the YouTube song duration.", 4000);
+							}
 						}
 					}
 				}

+ 13 - 4
frontend/components/Admin/Songs.vue

@@ -112,9 +112,15 @@
 				else if (type == 'artists') this.editing.song.artists.splice(index, 1);
 			},
 			edit: function (song, index) {
-				this.editing = { index, song };
-				this.video.player.loadVideoById(song._id);
-				this.isEditActive = true;
+				if (this.video.player) {
+					this.video.player.loadVideoById(song._id);
+					let songCopy = {};
+					for (let n in song) {
+						songCopy[n] = song[n];
+					}
+					this.editing = { index, song: songCopy };
+					this.isEditActive = true;
+				}
 			},
 			save: function (song) {
 				let _this = this;
@@ -173,7 +179,10 @@
 						if (event.data == 1) {
 							let youtubeDuration = _this.video.player.getDuration();
 							youtubeDuration -= _this.editing.song.skipDuration;
-							if (_this.editing.song.duration > youtubeDuration) this.stopVideo();
+							if (_this.editing.song.duration > youtubeDuration) {
+								this.video.player.stopVideo();
+								Toast.methods.addToast("Video can't play. Specified duration is bigger than the YouTube song duration.", 4000);
+							}
 						}
 					}
 				}

+ 6 - 2
frontend/components/Admin/Stations.vue

@@ -57,7 +57,7 @@
 						</p>
 						<label class='label'>Genres</label>
 						<p class='control has-addons'>
-							<input class='input' id='new-genre' type='text' placeholder='Genre'>
+							<input class='input' id='new-genre' type='text' placeholder='Genre' v-on:keyup.enter='addGenre()'>
 							<a class='button is-info' href='#' @click='addGenre()'>Add genre</a>
 						</p>
 						<span class='tag is-info' v-for='(index, genre) in newStation.genres' track-by='$index'>
@@ -66,7 +66,7 @@
 						</span>
 						<label class='label'>Blacklisted Genres</label>
 						<p class='control has-addons'>
-							<input class='input' id='new-blacklisted-genre' type='text' placeholder='Blacklisted Genre'>
+							<input class='input' id='new-blacklisted-genre' type='text' placeholder='Blacklisted Genre' v-on:keyup.enter='addBlacklistedGenre()'>
 							<a class='button is-info' href='#' @click='addBlacklistedGenre()'>Add blacklisted genre</a>
 						</p>
 						<span class='tag is-info' v-for='(index, genre) in newStation.blacklistedGenres' track-by='$index'>
@@ -115,6 +115,10 @@
 					blacklistedGenres,
 				}, result => {
 					Toast.methods.addToast(result.message, 3000);
+					if (result.status == 'success') this.newStation = {
+						genres: [],
+						blacklistedGenres: []
+					}
 				});
 			},
 			removeStation: function (index) {

+ 7 - 5
frontend/components/MainFooter.vue

@@ -25,11 +25,13 @@
 </template>
 
 <style lang='scss' scoped>
-	.content a:not(.button) {
-		border: 0;
-	}
+	.content a:not(.button) { border: 0; }
 
-	.icon:visited {
-		color: #4a4a4a !important;
+	.content {
+		display: flex;
+		align-items: center;
+		flex-direction: column;
 	}
+
+	.icon:visited { color: #4a4a4a !important; }
 </style>

+ 3 - 3
frontend/components/Modals/AddSongToQueue.vue

@@ -4,7 +4,7 @@
 		<div class="modal-card">
 			<header class="modal-card-head">
 				<p class="modal-card-title">Add Songs to Station</p>
-				<button class="delete" @click="$parent.toggleModal('addSongToQueue')" ></button>
+				<button class="delete" @click="$parent.modals.addSongToQueue = !$parent.modals.addSongToQueue" ></button>
 			</header>
 			<section class="modal-card-body">
 				<aside class='menu' v-if='$parent.$parent.loggedIn && $parent.type === "community"'>
@@ -134,8 +134,8 @@
 			});
 		},
 		events: {
-			closeModal: function() {
-				this.$parent.toggleModal('addSongToQueue')
+			closeModal: function () {
+				this.$parent.modals.addSongToQueue = !this.$parent.modals.addSongToQueue;
 			}
 		}
 	}

+ 3 - 3
frontend/components/Modals/EditStation.vue

@@ -4,7 +4,7 @@
 		<div class='modal-card'>
 			<header class='modal-card-head'>
 				<p class='modal-card-title'>Edit station</p>
-				<button class='delete' @click='$parent.toggleModal("editStation")'></button>
+				<button class='delete' @click='$parent.modals.editStation = !$parent.modals.editStation'></button>
 			</header>
 			<section class='modal-card-body'>
 				<label class='label'>Display name</label>
@@ -102,7 +102,7 @@
 			updatePartyMode: function () {
 				this.socket.emit('stations.updatePartyMode', this.data.stationId, this.data.partyMode, res => {
 					if (res.status === 'success') {
-					this.$parent.station.partyMode = this.data.partyMode;
+						this.$parent.station.partyMode = this.data.partyMode;
 						return Toast.methods.addToast(res.message, 4000);
 					}
 					Toast.methods.addToast(res.message, 8000);
@@ -122,7 +122,7 @@
 		},
 		events: {
 			closeModal: function() {
-				this.$parent.toggleModal("editStation")
+				this.$parent.modals.editStation = !this.$parent.modals.editStation;
 			}
 		}
 	}

+ 3 - 3
frontend/components/Modals/Playlists/Create.vue

@@ -4,7 +4,7 @@
 		<div class='modal-card'>
 			<header class='modal-card-head'>
 				<p class='modal-card-title'>Create Playlist</p>
-				<button class='delete' @click='$parent.toggleModal("createPlaylist")'></button>
+				<button class='delete' @click='$parent.modals.createPlaylist = !$parent.modals.createPlaylist'></button>
 			</header>
 			<section class='modal-card-body'>
 				<p class='control is-expanded'>
@@ -39,7 +39,7 @@
 				_this.socket.emit('playlists.create', _this.playlist, res => {
 					Toast.methods.addToast(res.message, 3000);
 				});
-				this.$parent.toggleModal('createPlaylist');
+				this.$parent.modals.createPlaylist = !this.$parent.modals.createPlaylist;
 			}
 		},
 		ready: function () {
@@ -50,7 +50,7 @@
 		},
 		events: {
 			closeModal: function() {
-				this.$parent.toggleModal("createPlaylist");
+				this.$parent.modals.createPlaylist = !this.$parent.modals.createPlaylist;
 			}
 		}
 	}

+ 5 - 5
frontend/components/Modals/Playlists/Edit.vue

@@ -4,7 +4,7 @@
 		<div class='modal-card'>
 			<header class='modal-card-head'>
 				<p class='modal-card-title'>Editing: {{ playlist.displayName }}</p>
-				<button class='delete' @click='$parent.toggleModal("editPlaylist")'></button>
+				<button class='delete' @click='$parent.modals.editPlaylist = !$parent.modals.editPlaylist'></button>
 			</header>
 			<section class='modal-card-body'>
 				<aside class='menu' v-if='playlist.songs && playlist.songs.length > 0'>
@@ -145,20 +145,20 @@
 				_this.socket.emit('playlists.remove', _this.playlist._id, res => {
 					if (res.status === 'success') {
 						Toast.methods.addToast(res.message, 3000);
-						_this.$parent.toggleModal('editPlaylist');
+						_this.$parent.modals.editPlaylist = !_this.$parent.modals.editPlaylist;
 					}
 				});
 			},
 			promoteSong: function (songId) {
 				let _this = this;
 				_this.socket.emit('playlists.moveSongToTop', _this.playlist._id, songId, res => {
-					Toast.methods.toast(4000, res.message);
+					Toast.methods.addToast(res.message, 4000);
 				});
 			},
 			demoteSong: function (songId) {
 				let _this = this;
 				_this.socket.emit('playlists.moveSongToBottom', _this.playlist._id, songId, res => {
-					Toast.methods.toast(4000, res.message);
+					Toast.methods.addToast(res.message, 4000);
 				});
 			}
 		},
@@ -206,7 +206,7 @@
 		},
 		events: {
 			closeModal: function() {
-				this.$parent.toggleModal("editPlaylist");
+				this.$parent.modals.editPlaylist = !this.$parent.modals.editPlaylist;
 			}
 		}
 	}

+ 1 - 1
frontend/components/Modals/Report.vue

@@ -198,7 +198,7 @@
 		},
 		events: {
 			closeModal: function () {
-				this.$parent.toggleModal('report');
+				this.$parent.modals.report = !this.$parent.modals.report;
 			}
 		},
 		ready: function () {

+ 1 - 1
frontend/components/Sidebars/Playlist.vue

@@ -22,7 +22,7 @@
 
 			<div class='none-found' v-else>No Playlists found</div>
 
-			<a class='button create-playlist' href='#' @click='$parent.toggleModal("createPlaylist")'>Create Playlist</a>
+			<a class='button create-playlist' href='#' @click='$parent.modals.createPlaylist = !$parent.modals.createPlaylist'>Create Playlist</a>
 		</div>
 	</div>
 </template>

+ 32 - 24
frontend/components/Sidebars/Queue.vue → frontend/components/Sidebars/SongsList.vue

@@ -1,9 +1,10 @@
 <template>
-	<div class='sidebar' transition='slide' v-if='$parent.sidebars.queue'>
+	<div class='sidebar' transition='slide' v-if='$parent.sidebars.songslist'>
 		<div class='inner-wrapper'>
-			<div class='title'>Queue</div>
+			<div class='title' v-if='$parent.type === "community"'>Queue</div>
+			<div class='title' v-else>Playlist</div>
 
-			<article class="media">
+			<article class="media" v-if="!$parent.noSong">
 				<figure class="media-left">
 					<p class="image is-64x64">
 						<img :src="$parent.currentSong.thumbnail" onerror="this.src='/assets/notes-transparent.png'">
@@ -18,9 +19,12 @@
 						</p>
 					</div>
 				</div>
+				<div class="media-right">
+					{{ $parent.formatTime($parent.currentSong.duration) }}
+				</div>
 			</article>
 
-			<article class="media" v-for='song in $parent.queue'>
+			<article class="media" v-for='song in $parent.songsList'>
 				<div class="media-content">
 					<div class="content">
 						<p>
@@ -30,9 +34,11 @@
 						</p>
 					</div>
 				</div>
+				<div class="media-right">
+					{{ $parent.$parent.formatTime(song.duration) }}
+				</div>
 			</article>
-
-			<a class='button add-to-queue' href='#' @click='$parent.toggleModal("addSongToQueue")'>Add Song to Queue</a>
+			<a class='button add-to-queue' href='#' @click='$parent.modals.addSongToQueue = !$parent.modals.addSongToQueue' v-if="$parent.type === 'community'">Add Song to Queue</a>
 		</div>
 	</div>
 </template>
@@ -41,19 +47,17 @@
 	import io from '../../io';
 
 	export default {
-		data() {
+		data: function () {
 			return {
-				playlist: []
+
 			}
 		},
 		ready: function () {
-			let _this = this;
+			/*let _this = this;
 			io.getSocket((socket) => {
 				_this.socket = socket;
-				_this.socket.emit('stations.getPlaylist', _this.$parent.stationId, res => {
-					if (res.status == 'success') _this.playlist = res.data;
-				});
-			});
+
+			});*/
 		}
 	}
 </script>
@@ -73,6 +77,8 @@
 	.inner-wrapper {	
 		top: 64px;
 		position: relative;
+		overflow: auto;
+		height: 100%;
 	}
 
 	.slide-transition {
@@ -90,25 +96,27 @@
 		font-weight: 600;
 	}
 
+	.media { padding: 0px 25px;}
+
+	.media-content .content {
+		height: 64px;
+		display: flex;
+		align-items: center;
+
+		strong { word-break: break-word; }
+	}
+
 	.add-to-queue {
 		width: 100%;
-    	margin-top: 25px;
+		margin-top: 25px;
 		height: 40px;
 		border-radius: 0;
 		background: rgb(3, 169, 244);
-    	color: #fff !important;
+		color: #fff !important;
 		border: 0;
-
 		&:active, &:focus { border: 0; }
 	}
-
 	.add-to-queue:focus { background: #029ce3; }
 
-	.media { padding: 0px 25px;}
-
-	.media-content .content {
-		height: 64px;
-		display: flex;
-		align-items: center;
-	}
+	.media-right { line-height: 64px; }
 </style>

+ 5 - 5
frontend/components/Station/CommunityHeader.vue

@@ -4,7 +4,7 @@
 			<a class='nav-item logo' href='#' v-link='{ path: "/" }' @click='this.$dispatch("leaveStation", title)'>
 				Musare
 			</a>
-			<a class='nav-item' href='#' v-if='isOwner()' @click='$parent.toggleModal("editStation")'>
+			<a class='nav-item' href='#' v-if='isOwner()' @click='$parent.modals.editStation = !$parent.modals.editStation'>
 				<span class='icon'>
 					<i class='material-icons'>settings</i>
 				</span>
@@ -23,7 +23,7 @@
 				<span class='icon'>
 					<i class='material-icons'>skip_next</i>
 				</span>
-				<span class="skip-votes">{{$parent.currentSong.skipVotes}}</span>
+				<span class="skip-votes">{{ $parent.currentSong.skipVotes }}</span>
 			</a>
 			<a class='nav-item' href='#' v-if='isOwner() && $parent.paused' @click='$parent.resumeStation()'>
 				<span class='icon'>
@@ -48,7 +48,7 @@
 		</span>
 
 		<div class="nav-right nav-menu" :class="{ 'is-active': isMobile }">
-			<a class='nav-item' href='#' @click='$parent.sidebars.queue = !$parent.sidebars.queue' v-if='$parent.station.partyMode === true'>
+			<a class='nav-item' href='#' @click='$parent.toggleSidebar("songslist")' v-if='$parent.station.partyMode === true'>
 				<span class='icon'>
 					<i class='material-icons'>queue_music</i>
 				</span>
@@ -58,12 +58,12 @@
 					<i class='material-icons'>chat</i>
 				</span>
 			</a>-->
-			<!--<a class='nav-item' href='#' @click='$parent.sidebars.users = !$parent.sidebars.users'>
+			<!--<a class='nav-item' href='#' @click='$parent.toggleSidebar("users")'>
 				<span class='icon'>
 					<i class='material-icons'>people</i>
 				</span>
 			</a>-->
-			<a class='nav-item' href='#' @click='$parent.sidebars.playlist = !$parent.sidebars.playlist' v-if='$parent.$parent.loggedIn'>
+			<a class='nav-item' href='#' @click='$parent.toggleSidebar("playlist")' v-if='$parent.$parent.loggedIn'>
 				<span class='icon'>
 					<i class='material-icons'>library_music</i>
 				</span>

+ 4 - 9
frontend/components/Station/OfficialHeader.vue

@@ -4,12 +4,12 @@
 			<a class='nav-item logo' href='#' v-link='{ path: "/" }' @click='this.$dispatch("leaveStation", title)'>
 				Musare
 			</a>
-			<a class='nav-item' href='#' v-if='isOwner()' @click='$parent.toggleModal("editStation")'>
+			<a class='nav-item' href='#' v-if='isOwner()' @click='$parent.modals.editStation = !$parent.modals.editStation'>
 				<span class='icon'>
 					<i class='material-icons'>settings</i>
 				</span>
 			</a>
-			<a class='nav-item' href='#' @click='$parent.toggleModal("addSongToQueue")' v-if='$parent.type === "official" && $parent.$parent.loggedIn'>
+			<a class='nav-item' href='#' @click='$parent.modals.addSongToQueue = !$parent.modals.addSongToQueue' v-if='$parent.type === "official" && $parent.$parent.loggedIn'>
 				<span class='icon'>
 					<i class='material-icons'>queue_music</i>
 				</span>
@@ -53,7 +53,7 @@
 		</span>
 
 		<div class="nav-right nav-menu" :class="{ 'is-active': isMobile }">
-			<a class='nav-item' href='#' @click='$parent.sidebars.queue = !$parent.sidebars.queue' v-if='$parent.station.partyMode === true'>
+			<a class='nav-item' href='#' @click='$parent.toggleSidebar("songslist")'>
 				<span class='icon'>
 					<i class='material-icons'>queue_music</i>
 				</span>
@@ -63,16 +63,11 @@
 					<i class='material-icons'>chat</i>
 				</span>
 			</a>-->
-			<!--<a class='nav-item' href='#' @click='$parent.sidebars.users = !$parent.sidebars.users'>
+			<!--<a class='nav-item' href='#' @click='$parent.toggleSidebar("users")'>
 				<span class='icon'>
 					<i class='material-icons'>people</i>
 				</span>
 			</a>-->
-			<!--a class='nav-item' href='#' @click='$parent.sidebars.playlist = !$parent.sidebars.playlist'>
-				<span class='icon'>
-					<i class='material-icons'>library_music</i>
-				</span>
-			</a-->
 		</div>
 	</nav>
 </template>

+ 50 - 27
frontend/components/Station/Station.vue

@@ -8,7 +8,7 @@
 	<edit-station v-if='modals.editStation'></edit-station>
 	<report v-if='modals.report'></report>
 
-	<queue-sidebar v-if='sidebars.queue'></queue-sidebar>
+	<songs-list-sidebar v-if='sidebars.songslist'></songs-list-sidebar>
 	<playlist-sidebar v-if='sidebars.playlist'></playlist-sidebar>
 	<users-sidebar v-if='sidebars.users'></users-sidebar>
 	
@@ -21,7 +21,7 @@
 			<h4 v-if='type === "community" && !station.partyMode && $parent.userId === station.owner && !station.privatePlaylist'>
 				<a href='#' class='no-song' @click='sidebars.playlist = true'>Play a private playlist</a>
 			</h4>
-			<h1 v-if='type === "community" && !station.partyMode && $parent.userId === station.owner && station.privatePlaylist'>Maybe you can add some songs to your selected private playlist</h1>
+			<h1 v-if='type === "community" && !station.partyMode && $parent.userId === station.owner && station.privatePlaylist'>Maybe you can add some songs to your selected private playlist and then press the skip button</h1>
 		</div>
 		<div class="columns is-mobile" v-show="!noSong">
 			<div class="column is-8-desktop is-offset-2-desktop is-12-mobile">
@@ -82,7 +82,7 @@
 	import EditStation from '../Modals/EditStation.vue';
 	import Report from '../Modals/Report.vue';
 
-	import QueueSidebar from '../Sidebars/Queue.vue';
+	import SongsListSidebar from '../Sidebars/SongsList.vue';
 	import PlaylistSidebar from '../Sidebars/Playlist.vue';
 	import UsersSidebar from '../Sidebars/UsersList.vue';
 
@@ -111,13 +111,13 @@
 					report: false
 				},
 				sidebars: {
-					queue: false,
+					songslist: false,
 					users: false,
 					playlist: false
 				},
 				noSong: false,
 				simpleSong: false,
-				queue: [],
+				songsList: [],
 				timeBeforePause: 0,
 				station: {},
 				skipVotes: 0,
@@ -137,6 +137,12 @@
 				else if (type == 'editStation') this.modals.editStation = !this.modals.editStation;
 				else if (type == 'report') this.modals.report = !this.modals.report;
 			},
+			toggleSidebar: function (type) {
+				Object.keys(this.sidebars).forEach(sidebar => {
+					if (sidebar !== type) this.sidebars[sidebar] = false;
+					else this.sidebars[type] = !this.sidebars[type];
+				});
+			},
 			youtubeReady: function() {
 				let local = this;
 				if (!local.player) {
@@ -290,24 +296,26 @@
 				let _this = this;
 				let isInQueue = false;
 				let userId = _this.$parent.userId;
-				_this.queue.forEach((queueSong) => {
-					if (queueSong.requestedBy === userId) isInQueue = true;
-				});
-				if (!isInQueue && _this.privatePlaylistQueueSelected) {
-
-					_this.socket.emit('playlists.getFirstSong', _this.privatePlaylistQueueSelected, data => {
-						if (data.status === 'success') {
-							let songId = data.song._id;
-							_this.automaticallyRequestedSongId = songId;
-							_this.socket.emit('stations.addToQueue', _this.stationId, songId, data => {
-								if (data.status === 'success') {
-									_this.socket.emit('playlists.moveSongToBottom', _this.privatePlaylistQueueSelected, songId, data => {
-										if (data.status === 'success') {}
-									});
-								}
-							});
-						}
+				if (_this.type === 'community') {
+					_this.songsList.forEach((queueSong) => {
+						if (queueSong.requestedBy === userId) isInQueue = true;
 					});
+					if (!isInQueue && _this.privatePlaylistQueueSelected) {
+
+						_this.socket.emit('playlists.getFirstSong', _this.privatePlaylistQueueSelected, data => {
+							if (data.status === 'success') {
+								let songId = data.song._id;
+								_this.automaticallyRequestedSongId = songId;
+								_this.socket.emit('stations.addToQueue', _this.stationId, songId, data => {
+									if (data.status === 'success') {
+										_this.socket.emit('playlists.moveSongToBottom', _this.privatePlaylistQueueSelected, songId, data => {
+											if (data.status === 'success') {}
+										});
+									}
+								});
+							}
+						});
+					}
 				}
 			},
 			joinStation: function () {
@@ -347,10 +355,14 @@
 						}
 						if (_this.type === 'community') {
 							_this.socket.emit('stations.getQueue', _this.stationId, data => {
-								if (data.status === 'success') _this.queue = data.queue;
+								if (data.status === 'success') _this.songsList = data.queue;
 							});
 						}
 					}
+
+					_this.socket.emit('stations.getPlaylist', _this.stationId, res => {
+				 		if (res.status == 'success') _this.songsList = res.data;
+				 	});
 				});
 			}
 		},
@@ -401,7 +413,7 @@
 
 					let isInQueue = false;
 					let userId = _this.$parent.userId;
-					_this.queue.forEach((queueSong) => {
+					_this.songsList.forEach((queueSong) => {
 						if (queueSong.requestedBy === userId) isInQueue = true;
 					});
 					if (!isInQueue && _this.privatePlaylistQueueSelected && (_this.automaticallyRequestedSongId !== _this.currentSong._id || !_this.currentSong._id)) {
@@ -458,7 +470,7 @@
 				});
 
 				_this.socket.on('event:queue.update', queue => {
-					if (this.type === 'community') this.queue = queue;
+					if (this.type === 'community') this.songsList = queue;
 				});
 
 				_this.socket.on('event:song.voteSkipSong', () => {
@@ -476,6 +488,13 @@
 						this.station.partyMode = partyMode;
 					}
 				});
+
+				_this.socket.on('event:newOfficialPlaylist', (playlist) => {
+					console.log(playlist);
+					if (this.type === 'official') {
+						this.songsList = playlist;
+					}
+				});
 			});
 
 			
@@ -492,7 +511,7 @@
 			CreatePlaylist,
 			EditStation,
 			Report,
-			QueueSidebar,
+			SongsListSidebar,
 			PlaylistSidebar,
 			UsersSidebar
 		}
@@ -809,7 +828,11 @@
 	}
 
 	.menu-list a {
-		padding: 0 10px !important;
+		/*padding: 0 10px !important;*/
+	}
+
+	.menu-list a:hover {
+		background-color : transparent;
 	}
 
 	.icons-group { display: flex; }

+ 53 - 28
frontend/components/pages/Home.vue

@@ -3,7 +3,7 @@
 		<main-header></main-header>
 		<div class="group">
 			<div class="group-title">Official Stations</div>
-			<div class="card station-card" v-for="station in stations.official" v-link="{ path: '/official/' + station._id }" @click="this.$dispatch('joinStation', station._id)" :class="station.class">
+			<div class="card station-card" v-for="station in stations.official" v-link="{ path: '/official/' + station._id }" @click="this.$dispatch('joinStation', station._id)">
 				<div class="card-image">
 					<figure class="image is-square">
 						<img :src="station.currentSong.thumbnail" onerror="this.src='/assets/notes-transparent.png'" />
@@ -14,22 +14,21 @@
 						<div class="media-left displayName">
 							<h5>{{ station.displayName }}</h5>
 						</div>
-						<div class="media-content"></div>
-						<div class="media-right">
-							<div>{{ station.userCount }}&nbsp;&nbsp;<i class="material-icons">perm_identity</i></div>
-						</div>
 					</div>
 
 					<div class="content">
 						{{ station.description }}
 					</div>
 				</div>
-				<a @click="this.$dispatch('joinStation', station._id)" href='#' class='absolute-a'></a>
+				<a @click="this.$dispatch('joinStation', station._id)" href='#' class='absolute-a' v-link="{ path: '/official/' + station._id }"></a>
+				<div v-if="station.privacy !== 'public'" title="This station is not visible to other users." class="station-status">
+					<i class='material-icons'>lock</i>
+				</div>
 			</div>
 		</div>
 		<div class="group">
 			<div class="group-title">Community Stations <a @click="toggleModal('createCommunityStation')" v-if="$parent.loggedIn" href='#'><i class="material-icons community-button">add_circle_outline</i></a></div>
-			<div class="card station-card" v-for="station in stations.community" v-link="{ path: '/community/' + station._id }" @click="this.$dispatch('joinStation', station._id)" :class="station.class">
+			<div class="card station-card" v-for="station in stations.community" v-link="{ path: '/community/' + station._id }" @click="this.$dispatch('joinStation', station._id)">
 				<div class="card-image">
 					<figure class="image is-square">
 						<img :src="station.currentSong.thumbnail" onerror="this.src='/assets/notes-transparent.png'" />
@@ -40,17 +39,19 @@
 						<div class="media-left displayName">
 							<h5>{{ station.displayName }}</h5>
 						</div>
-						<div class="media-content"></div>
-						<div class="media-right">
-							<div>{{ station.userCount }}&nbsp;&nbsp;<i class="material-icons">perm_identity</i></div>
-						</div>
 					</div>
 
 					<div class="content">
 						{{ station.description }}
 					</div>
 				</div>
-				<a @click="this.$dispatch('joinStation', station._id)" href='#' class='absolute-a'></a>
+				<a @click="this.$dispatch('joinStation', station._id)" href='#' class='absolute-a' v-link="{ path: '/community/' + station._id }"></a>
+				<div v-if="station.privacy !== 'public'" title="This station is not visible to other users." class="station-status">
+					<i class='material-icons'>lock</i>
+				</div>
+				<div v-if="isOwner(station)" title="This is your station." class="station-status">
+					<i class='material-icons'>home</i>
+				</div>
 			</div>
 		</div>
 		<main-footer></main-footer>
@@ -89,15 +90,25 @@
 					_this.socket.on('event:stations.created', station => {
 						if (!station.currentSong) station.currentSong = { thumbnail: '/assets/notes-transparent.png' };
 						if (station.currentSong && !station.currentSong.thumbnail) station.currentSong.thumbnail = "/assets/notes-transparent.png";
-						if (station.privacy !== 'public') {
-							station.class = {'station-red': true}
-						} else if (station.type === 'community') {
-							if (station.owner === userId) {
-								station.class = {'station-blue': true}
-							}
-						}
 						_this.stations[station.type].push(station);
 					});
+					_this.socket.on('event:station.nextSong', (stationId, newSong) => {
+						_this.stations.official.forEach((station) => {
+							if (station._id === stationId) {
+								if (!newSong) newSong = { thumbnail: '/assets/notes-transparent.png' };
+								if (newSong && !newSong.thumbnail) newSong.thumbnail = "/assets/notes-transparent.png";
+								station.currentSong = newSong;
+							}
+						});
+
+						_this.stations.community.forEach((station) => {
+							if (station._id === stationId) {
+								if (!newSong) newSong = { thumbnail: '/assets/notes-transparent.png' };
+								if (newSong && !newSong.thumbnail) newSong.thumbnail = "/assets/notes-transparent.png";
+								station.currentSong = newSong;
+							}
+						});
+					});
 				});
 			});
 		},
@@ -114,13 +125,8 @@
 						if (data.status === "success") data.stations.forEach(station => {
 							if (!station.currentSong) station.currentSong = { thumbnail: '/assets/notes-transparent.png' };
 							if (station.currentSong && !station.currentSong.thumbnail) station.currentSong.thumbnail = "/assets/notes-transparent.png";
-							if (station.privacy !== 'public') {
-								station.class = { 'station-red': true }
-							} else if (station.type === 'community') {
-								if (station.owner === userId) {
-									station.class = { 'station-blue': true }
-								}
-							}
+							if (station.privacy !== 'public') station.class = { 'station-red': true }
+							else if (station.type === 'community' && station.owner === userId) station.class = { 'station-blue': true }
 							if (station.type == 'official') _this.stations.official.push(station);
 							else _this.stations.community.push(station);
 						});
@@ -128,6 +134,10 @@
 					_this.socket.emit("apis.joinRoom", 'home', () => {
 					});
 				});
+			},
+			isOwner: function(station) {
+				let _this = this;
+				return station.owner === _this.$parent.userId && station.privacy === 'public';
 			}
 		},
 		components: { MainHeader, MainFooter }
@@ -164,6 +174,22 @@
 		html { font-size: 14px; }
 	}
 
+	.station-status {
+		width: 40px;
+		height: 40px;
+		position: absolute;
+		top: 0;
+		right: 0;
+		background-color: rgba(255, 255, 255, 1);
+		border-bottom-left-radius: 2px;
+		i {
+			font-size: 40px;
+			text-align: center;
+			line-height: 40px;
+			margin-left: 1px;
+		}
+	}
+
 	.group { min-height: 64px; }
 
 	.station-card {
@@ -180,8 +206,7 @@
 
 	.community-button:hover { color: #03a9f4; }
 
-	.station-blue { outline: 5px solid #03a9f4; }
-	.station-red { outline: 5px solid #f45703; }
+	.station-privacy { text-transform: capitalize; }
 
 	.label { display: flex; }
 

+ 1 - 1
frontend/package.json

@@ -34,7 +34,7 @@
   "dependencies": {
     "node-sass": "^3.13.0",
     "vue": "^1.0.26",
-    "vue-roaster": "^1.1.0",
+    "vue-roaster": "^1.1.1",
     "vue-router": "^0.7.13"
   }
 }