瀏覽代碼

Merge pull request #14 from Musare/staging

Beta Release Day 7
Jonathan 8 年之前
父節點
當前提交
4ccf290003
共有 36 個文件被更改,包括 372 次插入251 次删除
  1. 4 0
      backend/index.js
  2. 10 2
      backend/logic/actions/apis.js
  3. 10 6
      backend/logic/actions/queueSongs.js
  4. 8 5
      backend/logic/actions/reports.js
  5. 25 5
      backend/logic/actions/songs.js
  6. 19 16
      backend/logic/actions/stations.js
  7. 0 1
      backend/logic/actions/users.js
  8. 11 11
      backend/logic/app.js
  9. 1 1
      backend/logic/db/schemas/report.js
  10. 0 1
      backend/logic/notifications.js
  11. 5 42
      backend/logic/stations.js
  12. 1 1
      backend/logic/utils.js
  13. 5 1
      frontend/App.vue
  14. 55 0
      frontend/build/assets/discord.svg
  15. 二進制
      frontend/build/assets/notes-transparent.png
  16. 二進制
      frontend/build/assets/notes.png
  17. 21 4
      frontend/components/Admin/QueueSongs.vue
  18. 24 5
      frontend/components/Admin/Songs.vue
  19. 23 5
      frontend/components/Admin/Stations.vue
  20. 16 7
      frontend/components/MainFooter.vue
  21. 7 1
      frontend/components/Modals/AddSongToQueue.vue
  22. 1 1
      frontend/components/Modals/EditSong.vue
  23. 23 8
      frontend/components/Modals/EditStation.vue
  24. 5 12
      frontend/components/Modals/Playlists/Edit.vue
  25. 8 9
      frontend/components/Modals/Report.vue
  26. 2 2
      frontend/components/Sidebars/Playlist.vue
  27. 3 2
      frontend/components/Sidebars/Queue.vue
  28. 1 0
      frontend/components/Sidebars/UsersList.vue
  29. 7 7
      frontend/components/Station/CommunityHeader.vue
  30. 10 10
      frontend/components/Station/OfficialHeader.vue
  31. 33 57
      frontend/components/Station/Station.vue
  32. 2 1
      frontend/components/User/Settings.vue
  33. 2 1
      frontend/components/User/Show.vue
  34. 18 20
      frontend/components/pages/Home.vue
  35. 11 6
      frontend/io.js
  36. 1 1
      frontend/package.json

+ 4 - 0
backend/index.js

@@ -14,6 +14,10 @@ const cache = require('./logic/cache');
 const notifications = require('./logic/notifications');
 const config = require('config');
 
+process.on('uncaughtException', err => {
+	console.log(`ERROR: ${err}`);
+});
+
 async.waterfall([
 
 	// setup our Redis cache

+ 10 - 2
backend/logic/actions/apis.js

@@ -2,7 +2,8 @@
 
 const request = require('request'),
 	  config  = require('config'),
-		utils = require('../utils');
+		utils = require('../utils'),
+		hooks = require('./hooks');
 
 module.exports = {
 
@@ -40,6 +41,13 @@ module.exports = {
 			utils.socketJoinRoom(session.socketId, page);
 		}
 		cb({});
-	}
+	},
+
+	joinAdminRoom: hooks.adminRequired((session, page, cb) => {
+		if (page === 'queue' || page === 'songs' || page === 'stations' || page === 'reports') {
+			utils.socketJoinRoom(session.socketId, `admin.${page}`);
+		}
+		cb({});
+	})
 
 };

+ 10 - 6
backend/logic/actions/queueSongs.js

@@ -9,15 +9,19 @@ const config = require('config');
 const request = require('request');
 const hooks = require('./hooks');
 
-notifications.subscribe('queue.newSong', songId => {
-	utils.emitToRoom('admin.queue', 'event:song.new', { songId });
+cache.sub('queue.newSong', songId => {
+	console.log(123321);
+	db.models.queueSong.findOne({_id: songId}, (err, song) => {
+		console.log(err, song);
+		utils.emitToRoom('admin.queue', 'event:admin.queueSong.added', song);
+	});
 });
 
-notifications.subscribe('queue.removedSong', songId => {
-	utils.emitToRoom('admin.queue', 'event:song.removed', { songId });
+cache.sub('queue.removedSong', songId => {
+	utils.emitToRoom('admin.queue', 'event:admin.queueSong.removed', songId);
 });
 
-notifications.subscribe('queue.updatedSong', songId => {
+cache.sub('queue.updatedSong', songId => {
 	//TODO Retrieve new Song object
 	utils.emitToRoom('admin.queue', 'event:song.updated', { songId });
 });
@@ -51,7 +55,7 @@ module.exports = {
 	remove: hooks.adminRequired((session, songId, cb) => {
 		db.models.queueSong.remove({ _id: songId }, (err, res) => {
 			if (err) return cb({ status: 'failure', message: err.message });
-			//TODO Pub/sub for (queue)songs on admin pages.
+			cache.pub('queue.removedSong', songId);
 			cb({ status: 'success', message: 'Song was removed successfully' });
 		});
 	}),

+ 8 - 5
backend/logic/actions/reports.js

@@ -4,6 +4,7 @@ const async = require('async');
 
 const db = require('../db');
 const hooks = require('./hooks');
+const songs = require('../songs');
 
 module.exports = {
 
@@ -25,14 +26,14 @@ module.exports = {
 		});
 	}),
 
-	create: hooks.loginRequired((session, data, cb) => {
+	create: hooks.loginRequired((session, data, cb, userId) => {
 		async.waterfall([
 
 			(next) => {
-				db.models.report.find({ createdBy: data.createdBy, createdAt: data.createdAt }).exec((err, report) => {
-					if (err) console.error(err);
-					if (report) return cb({ status: 'failure', message: 'Report already exists' });
-					else next();
+				songs.getSong(data.songId, (err, song) => {
+					if (err) return next(err);
+					if (!song) return next('Song does not exist in our Database.');
+					next();
 				});
 			},
 
@@ -93,6 +94,8 @@ module.exports = {
 			},
 
 			(next) => {
+				data.createdBy = userId;
+				data.createdAt = Date.now();
 				db.models.report.create(data, next);
 			}
 

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

@@ -8,6 +8,16 @@ const utils = require('../utils');
 const hooks = require('./hooks');
 const queueSongs = require('./queueSongs');
 
+cache.sub('song.removed', songId => {
+	utils.emitToRoom('admin.songs', 'event:admin.song.removed', songId);
+});
+
+cache.sub('song.added', songId => {
+	db.models.queueSong.findOne({_id: songId}, (err, song) => {
+		utils.emitToRoom('admin.songs', 'event:admin.song.added', song);
+	});
+});
+
 cache.sub('song.like', (data) => {
 	utils.emitToRoom(`song.${data.songId}`, 'event:song.like', {songId: data.songId, undisliked: data.undisliked});
 	utils.socketsFromUser(data.userId, (sockets) => {
@@ -64,7 +74,14 @@ module.exports = {
 	}),
 
 	remove: hooks.adminRequired((session, songId, cb) => {
-		db.models.song.remove({ _id: songId });
+		db.models.song.remove({ _id: songId }, (err) => {
+			if (err) return cb({status: 'failure', message: err.message});
+			cache.hdel('songs', songId, (err) => {
+				if (err) return cb({status: 'failure', message: err.message});
+				cache.pub('song.removed', songId);
+				cb({status: 'success', message: 'Successfully removed the song.'});
+			});
+		});
 	}),
 
 	add: hooks.adminRequired((session, song, cb, userId) => {
@@ -75,9 +92,13 @@ module.exports = {
 				newSong.acceptedBy = userId;
 				newSong.acceptedAt = Date.now();
 				if (!existingSong) newSong.save(err => {
-					console.log(err, 1);
-					if (err) console.error(err);
-					else cb({ status: 'success', message: 'Song has been moved from Queue' })
+					if (err) {
+						console.error(err);
+						cb({ status: 'failure', message: 'Something went wrong while adding the song to the queue.' });
+					} else {
+						cache.pub('song.added', songId);
+						cb({ status: 'success', message: 'Song has been moved from Queue' });
+					}
 				});
 			});
 			//TODO Check if video is in queue and Add the song to the appropriate stations
@@ -93,7 +114,6 @@ module.exports = {
 				if (!err) {
 					db.models.user.update({_id: userId}, {$push: {liked: songId}, $pull: {disliked: songId}}, err => {
 						if (!err) {
-							console.log(JSON.stringify({ songId, userId: userId }));
 							songs.updateSong(songId, (err, song) => {});
 							cache.pub('song.like', JSON.stringify({ songId, userId: session.userId, undisliked: (dislikes === -1) }));
 						} else db.models.song.update({ _id: songId }, { $inc: { likes: -1, dislikes: -dislikes } }, err => {

+ 19 - 16
backend/logic/actions/stations.js

@@ -13,6 +13,14 @@ const stations = require('../stations');
 const songs = require('../songs');
 const hooks = require('./hooks');
 
+cache.sub('station.updatePartyMode', data => {
+	utils.emitToRoom(`station.${data.stationId}`, "event:partyMode.updated", data.partyMode);
+});
+
+cache.sub('privatePlaylist.selected', data => {
+	utils.emitToRoom(`station.${data.stationId}`, "event:privatePlaylist.selected", data.playlistId);
+});
+
 cache.sub('station.pause', stationId => {
 	utils.emitToRoom(`station.${stationId}`, "event:stations.pause");
 });
@@ -35,28 +43,25 @@ cache.sub('station.voteSkipSong', stationId => {
 	utils.emitToRoom(`station.${stationId}`, "event:song.voteSkipSong");
 });
 
+cache.sub('station.remove', stationId => {
+	utils.emitToRoom('admin.stations', 'event:admin.station.removed', stationId);
+});
+
 cache.sub('station.create', stationId => {
 	stations.initializeStation(stationId, (err, station) => {
-		console.log("*************", err, station);
-		//TODO Emit to admin station page
-
+		if (err) console.error(err);
+		utils.emitToRoom('admin.stations', 'event:admin.station.added', station);
 		// TODO If community, check if on whitelist
-		console.log("*************", station.privacy);
 		if (station.privacy === 'public') utils.emitToRoom('home', "event:stations.created", station);
 		else {
 			let sockets = utils.getRoomSockets('home');
-			console.log("*************", sockets.length);
 			for (let socketId in sockets) {
 				let socket = sockets[socketId];
 				let session = sockets[socketId].session;
-				console.log("*************", session);
 				if (session.sessionId) {
 					cache.hget('sessions', session.sessionId, (err, session) => {
-						console.log("*************", err, session);
 						if (!err && session) {
-							console.log("*************");
 							db.models.user.findOne({_id: session.userId}, (err, user) => {
-								console.log("*************", err, user.role, station.type, station.owner, session.userId);
 								if (user.role === 'admin') socket.emit("event:stations.created", station);
 								else if (station.type === "community" && station.owner === session.userId) socket.emit("event:stations.created", station);
 							});
@@ -92,7 +97,6 @@ module.exports = {
 			for (let prop in stations) {
 				// TODO If community, check if on whitelist
 				let station = stations[prop];
-				console.log(station)
 				if (station.privacy === 'public') add(true, station);
 				else if (!session.sessionId) add(false);
 				else {
@@ -113,11 +117,9 @@ module.exports = {
 			}
 
 			function add(add, station) {
-				console.log("ADD!", add, station);
 				if (add) arr.push(station);
 				done++;
 				if (done === Object.keys(stations).length) {
-					console.log("DONE!", done);
 					cb({ status: 'success', stations: arr });
 				}
 			}
@@ -212,7 +214,8 @@ module.exports = {
 								displayName: station.displayName,
 								privacy: station.privacy,
 								partyMode: station.partyMode,
-								owner: station.owner
+								owner: station.owner,
+								privatePlaylist: station.privatePlaylist
 							}
 						});
 					}
@@ -332,6 +335,7 @@ module.exports = {
 				if (err) return cb({ status: 'failure', message: 'Something went wrong when saving the station.' });
 				stations.updateStation(stationId, () => {
 					//TODO Pub/sub for privacy change
+					cache.pub('station.updatePartyMode', {stationId: stationId, partyMode: newPartyMode});
 					stations.skipStation(stationId)();
 					cb({ status: 'success', message: 'Successfully updated the party mode.' });
 				})
@@ -373,7 +377,6 @@ module.exports = {
 				if (station.paused) {
 					station.paused = false;
 					station.timePaused += (Date.now() - station.pausedAt);
-					console.log("&&&", station.timePaused, station.pausedAt, Date.now(), station.timePaused);
 					db.models.station.update({_id: stationId}, {$set: {paused: false}, $inc: {timePaused: Date.now() - station.pausedAt}}, () => {
 						stations.updateStation(stationId, (err, station) => {
 							cache.pub('station.resume', stationId);
@@ -391,9 +394,9 @@ module.exports = {
 
 	remove: hooks.ownerRequired((session, stationId, cb) => {
 		db.models.station.remove({ _id: stationId }, (err) => {
-			console.log(err, stationId);
 			if (err) return cb({status: 'failure', message: 'Something went wrong when deleting that station.'});
 			cache.hdel('stations', stationId, () => {
+				cache.pub('station.remove', stationId);
 				return cb({ status: 'success', message: 'Station successfully removed' });
 			});
 		});
@@ -484,7 +487,6 @@ module.exports = {
 					} else cont(song);
 					function cont(song) {
 						db.models.station.update({_id: stationId}, {$push: {queue: song}}, (err) => {
-							console.log(err);
 							if (err) return cb({'status': 'failure', 'message': 'Something went wrong.'});
 							stations.updateStation(stationId, (err, station) => {
 								if (err) return cb(err);
@@ -543,6 +545,7 @@ module.exports = {
 							stations.updateStation(stationId, (err, station) => {
 								if (err) return cb(err);
 								stations.skipStation(stationId)();
+								cache.pub('privatePlaylist.selected', {playlistId, stationId});
 								cb({'status': 'success', 'message': 'Playlist selected.'});
 							});
 						});

+ 0 - 1
backend/logic/actions/users.js

@@ -84,7 +84,6 @@ module.exports = {
 			// if it is, we check if a user with the requested username already exists
 			(response, body, next) => {
 				let json = JSON.parse(body);
-				console.log(response, body);
 				if (json.success !== true) return next('Response from recaptcha was not successful');
 				db.models.user.findOne({ username: new RegExp(`^${username}$`, 'i') }, next);
 			},

+ 11 - 11
backend/logic/app.js

@@ -56,7 +56,7 @@ const lib = {
 		});
 
 		function redirectOnErr (res, err){
-			return res.redirect(config.get('domain') + '/?err=' + encodeURIComponent('err'));
+			return res.redirect(`http://${config.get('domain')}/?err=${encodeURIComponent(err)}`);
 		}
 
 		app.get('/auth/github/authorize/callback', (req, res) => {
@@ -66,30 +66,30 @@ const lib = {
 						url: `https://api.github.com/user?access_token=${access_token}`,
 						headers: { 'User-Agent': 'request' }
 					}, (err, httpResponse, body) => {
-						if (err) return redirectOnErr(res, 'err');
+						if (err) return redirectOnErr(res, err.message);
 						body = JSON.parse(body);
 						db.models.user.findOne({'services.github.id': body.id}, (err, user) => {
 							if (err) return redirectOnErr(res, 'err');
 							if (user) {
 								user.services.github.access_token = access_token;
 								user.save(err => {
-									if (err) return redirectOnErr(res, 'err');
+									if (err) return redirectOnErr(res, err.message);
 									let sessionId = utils.guid();
 									cache.hset('sessions', sessionId, cache.schemas.session(sessionId, user._id), err => {
-										if (err) return redirectOnErr(res, 'err');
+										if (err) return redirectOnErr(res, err.message);
 										res.cookie('SID', sessionId);
 										res.redirect(`http://${config.get('domain')}/`);
 									});
 								});
 							} else {
 								db.models.user.findOne({ username: new RegExp(`^${body.login}$`, 'i') }, (err, user) => {
-									if (err) return redirectOnErr(res, 'err');
-									if (user) return redirectOnErr(res, 'err');
+									if (err) return redirectOnErr(res, err.message);
+									if (user) return redirectOnErr(res, 'An account with that username already exists.');
 									else request.get({
 										url: `https://api.github.com/user/emails?access_token=${access_token}`,
 										headers: {'User-Agent': 'request'}
 									}, (err, httpResponse, body2) => {
-										if (err) return redirectOnErr(res, 'err');
+										if (err) return redirectOnErr(res, err.message);
 										body2 = JSON.parse(body2);
 										let address;
 										if (!Array.isArray(body2)) return redirectOnErr(res, body2.message);
@@ -97,8 +97,8 @@ const lib = {
 											if (email.primary) address = email.email.toLowerCase();
 										});
 										db.models.user.findOne({'email.address': address}, (err, user) => {
-											if (err) return redirectOnErr(res, 'err');
-											if (user) return redirectOnErr(res, 'err');
+											if (err) return redirectOnErr(res, err.message);
+											if (user) return redirectOnErr(res, 'An account with that email address already exists.');
 											else db.models.user.create({
 												username: body.login,
 												email: {
@@ -109,11 +109,11 @@ const lib = {
 													github: {id: body.id, access_token}
 												}
 											}, (err, user) => {
-												if (err) return redirectOnErr(res, 'err');
+												if (err) return redirectOnErr(res, err.message);
 												//TODO Send verification email
 												let sessionId = utils.guid();
 												cache.hset('sessions', sessionId, cache.schemas.session(sessionId, user._id), err => {
-													if (err) return redirectOnErr(res, 'err');
+													if (err) return redirectOnErr(res, err.message);
 													res.cookie('SID', sessionId);
 													res.redirect(`http://${config.get('domain')}/`);
 												});

+ 1 - 1
backend/logic/db/schemas/report.js

@@ -1,7 +1,7 @@
 module.exports = {
 	resolved: { type: Boolean, default: false, required: true },
 	songId: { type: String, required: true },
-	description: { type: String, required: true },
+	description: { type: String },
 	issues: [{
 		name: String,
 		reasons: Array

+ 0 - 1
backend/logic/notifications.js

@@ -40,7 +40,6 @@ const lib = {
 	 * @param {Function} cb - gets called when the notification has been scheduled
 	 */
 	schedule: (name, time, cb) => {
-		console.log(time);
 		pub.set(crypto.createHash('md5').update(`_notification:${name}_`).digest('hex'), '', 'PX', time, 'NX', cb);
 	},
 

+ 5 - 42
backend/logic/stations.js

@@ -43,14 +43,11 @@ module.exports = {
 	},
 
 	initializeStation: function(stationId, cb) {
-		console.log(112233, stationId, cb);
 		if (typeof cb !== 'function') cb = ()=>{};
 		let _this = this;
 		_this.getStation(stationId, (err, station) => {
 			if (!err) {
-				console.log("###");
 				if (station) {
-					console.log("###1");
 					let notification = notifications.subscribe(`stations.nextSong?id=${station._id}`, _this.skipStation(station._id), true);
 					if (!station.paused ) {
 						/*if (!station.startedAt) {
@@ -62,9 +59,7 @@ module.exports = {
 							let timeLeft = ((station.currentSong.duration * 1000) - (Date.now() - station.startedAt - station.timePaused));
 							if (isNaN(timeLeft)) timeLeft = -1;
 							if (station.currentSong.duration * 1000 < timeLeft || timeLeft < 0) {
-								console.log("Test");
 								this.skipStation(station._id)((err, station) => {
-									console.log(45, err, station);
 									cb(err, station);
 								});
 							} else {
@@ -73,7 +68,6 @@ module.exports = {
 							}
 						} else {
 							_this.skipStation(station._id)((err, station) => {
-								console.log(47, err, station);
 								cb(err, station);
 							});
 						}
@@ -101,7 +95,6 @@ module.exports = {
 									let found = false;
 									song.genres.forEach((songGenre) => {
 										if (station.blacklistedGenres.indexOf(songGenre) !== -1) found = true;
-										console.log(songGenre, station.blacklistedGenres, station.blacklistedGenres.indexOf(songGenre), found);
 									});
 									if (!found) {
 										songList.push(song._id);
@@ -110,9 +103,7 @@ module.exports = {
 							});
 						}
 						genresDone.push(genre);
-						if (genresDone.length === station.genres.length) {
-							next();
-						}
+						if (genresDone.length === station.genres.length) next();
 					});
 				});
 			},
@@ -147,14 +138,12 @@ module.exports = {
 
 			(station, next) => {
 				if (station) return next(true, station);
-
 				db.models.station.findOne({ _id: stationId }, next);
 			},
 
 			(station, next) => {
 				if (station) {
 					station = cache.schemas.station(station);
-					console.log(1234321, stationId);
 					cache.hset('stations', stationId, station);
 					next(true, station);
 				} else next('Station not found.');
@@ -162,7 +151,6 @@ module.exports = {
 
 		], (err, station) => {
 			if (err && err !== true) cb(err);
-
 			cb(null, station);
 		});
 	},
@@ -176,7 +164,6 @@ module.exports = {
 
 			(station, next) => {
 				if (!station) return next('Station not found.');
-				console.log(123444321, stationId);
 				cache.hset('stations', stationId, station, (err) => {
 					if (err) return next(err);
 					next(null, station);
@@ -185,7 +172,6 @@ module.exports = {
 
 		], (err, station) => {
 			if (err && err !== true) cb(err);
-
 			cb(null, station);
 		});
 	},
@@ -194,17 +180,12 @@ module.exports = {
 		let _this = this;
 		return (cb) => {
 			if (typeof cb !== 'function') cb = ()=>{};
-			console.log("###2");
-			console.log("NOTIFICATION!!!");
 			_this.getStation(stationId, (err, station) => {
-				console.log("###3");
 				if (station) {
-					console.log("###4");
 					// notify all the sockets on this station to go to the next song
 					async.waterfall([
 
 						(next) => {
-							console.log("###5");
 							if (station.type === "official") {
 								if (station.playlist.length > 0) {
 									function func() {
@@ -238,9 +219,7 @@ module.exports = {
 										} else {
 											db.models.station.update({_id: station._id}, {$set: {currentSongIndex: 0}}, (err) => {
 												_this.updateStation(station._id, (err, station) => {
-													console.log(12345678, err, station);
 													_this.calculateSongForStation(station, (err, newPlaylist) => {
-														console.log('New playlist: ', newPlaylist);
 														if (!err) {
 															songs.getSong(newPlaylist[0], (err, song) => {
 																let $set = {};
@@ -314,9 +293,7 @@ module.exports = {
 							} else {
 								if (station.partyMode === true) {
 									if (station.queue.length > 0) {
-										console.log("##");
 										db.models.station.update({_id: stationId}, {$pull: {queue: {songId: station.queue[0]._id}}}, (err) => {
-											console.log("##1", err);
 											if (err) return next(err);
 											let $set = {};
 											$set.currentSong = station.queue[0];
@@ -328,21 +305,16 @@ module.exports = {
 											next(null, $set);
 										});
 									} else {
-										console.log("##2");
 										next(null, {currentSong: null});
 									}
 								} else {
 									db.models.playlist.findOne({_id: station.privatePlaylist}, (err, playlist) => {
-										console.log(station.privatePlaylist, err, playlist);
 										if (err || !playlist) return next(null, {currentSong: null});
 										playlist = playlist.songs;
 										if (playlist.length > 0) {
 											let $set = {};
-											if (station.currentSongIndex < playlist.length - 1) {
-												$set.currentSongIndex = station.currentSongIndex + 1;
-											} else {
-												$set.currentSongIndex = 0;
-											}
+											if (station.currentSongIndex < playlist.length - 1) $set.currentSongIndex = station.currentSongIndex + 1;
+											else $set.currentSongIndex = 0;
 											songs.getSong(playlist[$set.currentSongIndex]._id, (err, song) => {
 												if (!err && song) {
 													$set.currentSong = {
@@ -369,23 +341,17 @@ module.exports = {
 												$set.timePaused = 0;
 												next(null, $set);
 											});
-										} else {
-											next(null, {currentSong: null});
-										}
+										} else next(null, {currentSong: null});
 									});
 								}
 							}
 						},
 
 						($set, next) => {
-							console.log("$set", $set);
 							db.models.station.update({_id: station._id}, {$set}, (err) => {
-								console.log("##2.5", err);
 								_this.updateStation(station._id, (err, station) => {
-									console.log("##2.6", err);
-									if (station.type === 'community' && station.partyMode === true) {
+									if (station.type === 'community' && station.partyMode === true)
 										cache.pub('station.queueUpdate', stationId);
-									}
 									next(null, station);
 								});
 							});
@@ -393,7 +359,6 @@ module.exports = {
 
 
 					], (err, station) => {
-						console.log("##3", err);
 						if (!err) {
 							if (station.currentSong !== null && station.currentSong._id !== undefined) {
 								station.currentSong.skipVotes = 0;
@@ -406,12 +371,10 @@ module.exports = {
 							});
 							if (station.currentSong !== null && station.currentSong._id !== undefined) {
 								utils.socketsJoinSongRoom(utils.getRoomSockets(`station.${station._id}`), `song.${station.currentSong._id}`);
-								console.log("NEXT SONG!!!", station.currentSong);
 								if (!station.paused) {
 									notifications.schedule(`stations.nextSong?id=${station._id}`, station.currentSong.duration * 1000);
 								}
 							} else {
-								console.log("22", !!(station.currentSong));
 								utils.socketsLeaveSongRooms(utils.getRoomSockets(`station.${station._id}`), `song.${station.currentSong._id}`);
 							}
 							cb(null, station);

+ 1 - 1
backend/logic/utils.js

@@ -259,7 +259,7 @@ module.exports = {
 			});
 			dur = dur.replace(/([\d]*)M/, (v, v2) => {
 				v2 = Number(v2);
-				duration = (v2 * 60);
+				duration += (v2 * 60);
 				return '';
 			});
 			dur = dur.replace(/([\d]*)S/, (v, v2) => {

+ 5 - 1
frontend/App.vue

@@ -54,7 +54,7 @@
 				});
 			},
 			'submitOnEnter': (cb, event) => {
-				if (event.which == 13) b(); return false;
+				if (event.which == 13) cb();
 			}
 		},
 		ready: function () {
@@ -69,12 +69,16 @@
 			io.onConnect(() => {
 				_this.socketConnected = true;
 			});
+			io.onConnectError(() => {
+				_this.socketConnected = false;
+			});
 			io.onDisconnect(() => {
 				_this.socketConnected = false;
 			});
 			lofig.get('serverDomain', res => {
 				_this.serverDomain = res;
 			});
+			if (_this.$route.query.err) Toast.methods.addToast(_this.$route.query.err, 20000);
 		},
 		events: {
 			'register': function () {

文件差異過大導致無法顯示
+ 55 - 0
frontend/build/assets/discord.svg


二進制
frontend/build/assets/notes-transparent.png


二進制
frontend/build/assets/notes.png


+ 21 - 4
frontend/components/Admin/QueueSongs.vue

@@ -16,7 +16,7 @@
 				<tbody>
 					<tr v-for='(index, song) in songs' track-by='$index'>
 						<td>
-							<img class='song-thumbnail' :src='song.thumbnail' onerror="this.src='/assets/notes.png'">
+							<img class='song-thumbnail' :src='song.thumbnail' onerror="this.src='/assets/notes-transparent.png'">
 						</td>
 						<td>
 							<strong>{{ song.title }}</strong>
@@ -130,18 +130,35 @@
 				});
 			},
 			remove: function (id, index) {
-				this.songs.splice(index, 1);
 				this.socket.emit('queueSongs.remove', id, res => {
 					if (res.status == 'success') Toast.methods.addToast(res.message, 2000);
 				});
+			},
+			init: function() {
+				let _this = this;
+				_this.socket.emit('queueSongs.index', data => {
+					_this.songs = data;
+				});
+				_this.socket.emit('apis.joinAdminRoom', 'queue', data => {});
 			}
 		},
 		ready: function () {
 			let _this = this;
 			io.getSocket((socket) => {
 				_this.socket = socket;
-				_this.socket.emit('queueSongs.index', data => {
-					_this.songs = data;
+				if (_this.socket.connected) {
+					_this.init();
+					_this.socket.on('event:admin.queueSong.added', queueSong => {
+						_this.songs.push(queueSong);
+					});
+					_this.socket.on('event:admin.queueSong.removed', songId => {
+						_this.songs = _this.songs.filter(function(song) {
+							return song._id !== songId;
+						});
+					});
+				}
+				io.onConnect(() => {
+					_this.init();
 				});
 			});
 

+ 24 - 5
frontend/components/Admin/Songs.vue

@@ -16,7 +16,7 @@
 				<tbody>
 					<tr v-for='(index, song) in songs' track-by='$index'>
 						<td>
-							<img class='song-thumbnail' :src='song.thumbnail' onerror="this.src='/assets/notes.png'">
+							<img class='song-thumbnail' :src='song.thumbnail' onerror="this.src='/assets/notes-transparent.png'">
 						</td>
 						<td>
 							<strong>{{ song.title }}</strong>
@@ -41,6 +41,7 @@
 	import { Toast } from 'vue-roaster';
 
 	import EditSong from '../Modals/EditSong.vue';
+	import io from '../../io';
 
 	export default {
 		components: { EditSong },
@@ -123,18 +124,36 @@
 				});
 			},
 			remove: function (id, index) {
-				this.songs.splice(index, 1);
 				this.socket.emit('songs.remove', id, res => {
-					if (res.status == 'success') Toast.methods.addToast(res.message, 2000);
+					if (res.status == 'success') Toast.methods.addToast(res.message, 4000);
+					else Toast.methods.addToast(res.message, 8000);
 				});
+			},
+			init: function() {
+				let _this = this;
+				_this.socket.emit('songs.index', data => {
+					_this.songs = data;
+				});
+				_this.socket.emit('apis.joinAdminRoom', 'songs', data => {});
 			}
 		},
 		ready: function () {
 			let _this = this;
 			io.getSocket((socket) => {
 				_this.socket = socket;
-				_this.socket.emit('songs.index', data => {
-					_this.songs = data;
+				if (_this.socket.connected) {
+					_this.init();
+					_this.socket.on('event:admin.song.added', song => {
+						_this.songs.push(song);
+					});
+					_this.socket.on('event:admin.song.removed', songId => {
+						_this.songs = _this.songs.filter(function(song) {
+							return song._id !== songId;
+						});
+					});
+				}
+				io.onConnect(() => {
+					_this.init();
 				});
 			});
 

+ 23 - 5
frontend/components/Admin/Stations.vue

@@ -114,12 +114,12 @@
 					genres,
 					blacklistedGenres,
 				}, result => {
-					console.log(result);
+					// Toast
 				});
 			},
 			removeStation: function (index) {
 				this.socket.emit('stations.remove', this.stations[index]._id, res => {
-					if (res.status == 'success') this.stations.splice(index, 1); Toast.methods.addToast(res.message, 3000);
+					Toast.methods.addToast(res.message, 3000);
 				});
 			},
 			addGenre: function () {
@@ -137,14 +137,32 @@
 				if (genre) this.newStation.blacklistedGenres.push(genre);
 				else Toast.methods.addToast('Genre cannot be empty', 3000);
 			},
-			removeBlacklistedGenre: function (index) { this.newStation.blacklistedGenres.splice(index, 1); }
+			removeBlacklistedGenre: function (index) { this.newStation.blacklistedGenres.splice(index, 1); },
+			init: function() {
+				let _this = this;
+				_this.socket.emit('stations.index', data => {
+					_this.stations = data.stations;
+				});
+				_this.socket.emit('apis.joinAdminRoom', 'stations', data => {});
+			}
 		},
 		ready: function () {
 			let _this = this;
 			io.getSocket((socket) => {
 				_this.socket = socket;
-				_this.socket.emit('stations.index', data => {
-						_this.stations = data.stations;
+				if (_this.socket.connected) {
+					_this.init();
+				}
+				_this.socket.on('event:admin.station.added', station => {
+					_this.stations.push(station);
+				});
+				_this.socket.on('event:admin.station.removed', stationId => {
+					_this.stations = _this.stations.filter(function(station) {
+						return station._id !== stationId;
+					});
+				});
+				io.onConnect(() => {
+					_this.init();
 				});
 			});
 		}

+ 16 - 7
frontend/components/MainFooter.vue

@@ -1,13 +1,22 @@
 <template>
-	<footer class="footer">
-		<div class="container">
-			<div class="content has-text-centered">
+	<footer class='footer'>
+		<div class='container'>
+			<div class='content has-text-centered'>
 				<p>
-					© Copyright Musare 2015 - {{ new Date().getFullYear() }}
+					© Copyright Musare 2015 - 2016
 				</p>
 				<p>
-					<a class="icon" href="https://github.com/musare/musarenode">
-						<i class="fa fa-github"></i>
+					<a class='icon' href='https://github.com/Musare/MusareNode' title='GitHub Repository'>
+						<i class='fa fa-github'></i>
+					</a>
+					<a class='icon' href='https://twitter.com/MusareMusic' title='Twitter Account'>
+						<i class='fa fa-twitter'></i>
+					</a>
+					<a class='icon' href='https://www.facebook.com/MusareMusic/' title='Facebook Page'>
+						<i class='fa fa-facebook'></i>
+					</a>
+					<a class='icon' href='https://discord.gg/Y5NxYGP' title='Discord Server'>
+						<img src='/assets/discord.svg'/>
 					</a>
 				</p>
 			</div>
@@ -15,7 +24,7 @@
 	</footer>
 </template>
 
-<style lang="scss" scoped>
+<style lang='scss' scoped>
 	.content a:not(.button) {
 		border: 0;
 	}

+ 7 - 1
frontend/components/Modals/AddSongToQueue.vue

@@ -71,7 +71,13 @@
 			},
 			submitQuery: function () {
 				let _this = this;
-				_this.socket.emit('apis.searchYoutube', _this.querySearch, results => {
+				let query = _this.querySearch;
+				if (query.indexOf('&index=') !== -1) {
+					query = query.split('&index=');
+					query.pop();
+					query = query.join('');
+				}
+				_this.socket.emit('apis.searchYoutube', query, results => {
 					results = results.data;
 					_this.queryResults = [];
 					for (let i = 0; i < results.items.length; i++) {

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

@@ -28,7 +28,7 @@
 					</div>
 				</div>
 				<h5 class='has-text-centered'>Thumbnail Preview</h5>
-				<img class='thumbnail-preview' :src='$parent.editing.song.thumbnail' onerror="this.src='/assets/notes.png'">
+				<img class='thumbnail-preview' :src='$parent.editing.song.thumbnail' onerror="this.src='/assets/notes-transparent.png'">
 
 				<label class='label'>Thumbnail URL</label>
 				<p class='control'>

+ 23 - 8
frontend/components/Modals/EditStation.vue

@@ -10,7 +10,7 @@
 				<label class='label'>Display name</label>
 				<div class='control is-grouped'>
 					<p class='control is-expanded'>
-						<input class='input' type='text' placeholder='Station Display Name' v-model='$parent.station.displayName'>
+						<input class='input' type='text' placeholder='Station Display Name' v-model='data.displayName'>
 					</p>
 					<p class='control'>
 						<a class='button is-info' @click='updateDisplayName()'>Update</a>
@@ -19,7 +19,7 @@
 				<label class='label'>Description</label>
 				<div class='control is-grouped'>
 					<p class='control is-expanded'>
-						<input class='input' type='text' placeholder='Station Display Name' v-model='$parent.station.description'>
+						<input class='input' type='text' placeholder='Station Display Name' v-model='data.description'>
 					</p>
 					<p class='control'>
 						<a class='button is-info' @click='updateDescription()'>Update</a>
@@ -29,7 +29,7 @@
 				<div class='control is-grouped'>
 					<p class='control is-expanded'>
 						<span class='select'>
-							<select v-model='$parent.station.privacy'>
+							<select v-model='data.privacy'>
 								<option :value='"public"'>Public</option>
 								<option :value='"unlisted"'>Unlisted</option>
 								<option :value='"private"'>Private</option>
@@ -43,7 +43,7 @@
 				<div class='control is-grouped' v-if="$parent.type === 'community'">
 					<p class="control is-expanded">
 						<label class="checkbox">
-							<input type="checkbox" v-model="$parent.station.partyMode">
+							<input type="checkbox" v-model="data.partyMode">
 							Party mode
 						</label>
 					</p>
@@ -61,27 +61,37 @@
 	import io from '../../io';
 
 	export default {
+		data: function() {
+			return {
+				data: {
+					displayName: '',
+					description: '',
+					privacy: 'private',
+					partyMode: false
+				}
+			}
+		},
 		methods: {
 			updateDisplayName: function () {
-				this.socket.emit('stations.updateDisplayName', this.$parent.stationId, this.$parent.station.displayName, res => {
+				this.socket.emit('stations.updateDisplayName', this.data.stationId, this.data.displayName, res => {
 					if (res.status == 'success') return Toast.methods.addToast(res.message, 4000);
 					Toast.methods.addToast(res.message, 8000);
 				});
 			},
 			updateDescription: function () {
-				this.socket.emit('stations.updateDescription', this.$parent.stationId, this.$parent.station.description, res => {
+				this.socket.emit('stations.updateDescription', this.data.stationId, this.data.description, res => {
 					if (res.status == 'success') return Toast.methods.addToast(res.message, 4000);
 					Toast.methods.addToast(res.message, 8000);
 				});
 			},
 			updatePrivacy: function () {
-				this.socket.emit('stations.updatePrivacy', this.$parent.stationId, this.$parent.station.privacy, res => {
+				this.socket.emit('stations.updatePrivacy', this.data.stationId, this.data.privacy, res => {
 					if (res.status == 'success') return Toast.methods.addToast(res.message, 4000);
 					Toast.methods.addToast(res.message, 8000);
 				});
 			},
 			updatePartyMode: function () {
-				this.socket.emit('stations.updatePartyMode', this.$parent.stationId, this.$parent.station.partyMode, res => {
+				this.socket.emit('stations.updatePartyMode', this.data.stationId, this.data.partyMode, res => {
 					if (res.status == 'success') return Toast.methods.addToast(res.message, 4000);
 					Toast.methods.addToast(res.message, 8000);
 				});
@@ -92,6 +102,11 @@
 			io.getSocket((socket) => {
 				_this.socket = socket;
 			});
+			this.data.stationId = this.$parent.stationId;
+			this.data.partyMode = this.$parent.station.partyMode;
+			this.data.description = this.$parent.station.description;
+			this.data.privacy = this.$parent.station.privacy;
+			this.data.displayName = this.$parent.station.displayName;
 		},
 		events: {
 			closeModal: function() {

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

@@ -156,27 +156,20 @@
 			io.getSocket((socket) => {
 				_this.socket = socket;
 				_this.socket.emit('playlists.getPlaylist', _this.$parent.playlistBeingEdited, res => {
-						if (res.status == 'success') _this.playlist = res.data; _this.playlist.oldId = res.data._id;
+					if (res.status == 'success') _this.playlist = res.data; _this.playlist.oldId = res.data._id;
 				});
 				_this.socket.on('event:playlist.addSong', (data) => {
-					if (_this.playlist._id === data.playlistId) {
-						console.log("PUSH!");
-						_this.playlist.songs.push(data.song);
-					}
+					if (_this.playlist._id === data.playlistId) _this.playlist.songs.push(data.song);
 				});
 				_this.socket.on('event:playlist.removeSong', (data) => {
 					if (_this.playlist._id === data.playlistId) {
 						_this.playlist.songs.forEach((song, index) => {
-						if (song._id === data.songId) {
-							_this.playlist.songs.splice(index, 1);
-						}
-					});
+							if (song._id === data.songId) _this.playlist.songs.splice(index, 1);
+						});
 					}
 				});
 				_this.socket.on('event:playlist.updateDisplayName', (data) => {
-					if (_this.playlist._id === data.playlistId) {
-						_this.playlist.displayName = data.displayName;
-					}
+					if (_this.playlist._id === data.playlistId) _this.playlist.displayName = data.displayName;
 				});
 			});
 		},

+ 8 - 9
frontend/components/Modals/Report.vue

@@ -8,7 +8,7 @@
 			</header>
 			<section class='modal-card-body'>
 				<div class='columns song-types'>
-					<div class='column song-type' v-if='$parent.previousSong !== null'>
+					<div class='column song-type' v-if='$parent.previousSong !== null && $parent.previousSong.likes !== -1 && $parent.previousSong.dislikes !== -1'>
 						<div class='card is-fullwidth' :class="{ 'is-highlight-active': isPreviousSongActive }" @click="highlight('previousSong')">
 							<header class='card-header'>
 								<p class='card-header-title'>
@@ -19,7 +19,7 @@
 								<article class='media'>
 									<figure class='media-left'>
 										<p class='image is-64x64'>
-											<img :src='$parent.previousSong.thumbnail' onerror='this.src="/assets/notes.png"'>
+											<img :src='$parent.previousSong.thumbnail' onerror='this.src="/assets/notes-transparent.png"'>
 										</p>
 									</figure>
 									<div class='media-content'>
@@ -35,7 +35,7 @@
 							</div>
 						</div>
 					</div>
-					<div class='column song-type' v-if='$parent.currentSong !== null'>
+					<div class='column song-type' v-if='$parent.currentSong !== null && $parent.currentSong.likes !== -1 && $parent.currentSong.dislikes !== -1'>
 						<div class='card is-fullwidth'  :class="{ 'is-highlight-active': isCurrentSongActive }" @click="highlight('currentSong')">
 							<header class='card-header'>
 								<p class='card-header-title'>
@@ -46,7 +46,7 @@
 								<article class='media'>
 									<figure class='media-left'>
 										<p class='image is-64x64'>
-											<img :src='$parent.currentSong.thumbnail' onerror='this.src="/assets/notes.png"'>
+											<img :src='$parent.currentSong.thumbnail' onerror='this.src="/assets/notes-transparent.png"'>
 										</p>
 									</figure>
 									<div class='media-content'>
@@ -63,7 +63,8 @@
 						</div>
 					</div>
 				</div>
-				<div class='edit-report-wrapper'>
+				<h4 v-if='($parent.currentSong === null || ($parent.currentSong.likes === -1 && $parent.currentSong.dislikes === -1)) && ($parent.previousSong === null || ($parent.previousSong.likes === -1 || $parent.previousSong.dislikes === -1))'>There are currently no songs to report.</h4>
+				<div class='edit-report-wrapper' v-else>
 					<div class='columns is-multiline'>
 						<div class='column is-half' v-for='issue in issues'>
 							<label class='label'>{{ issue.name }}</label>
@@ -83,7 +84,7 @@
 				</div>
 			</section>
 			<footer class='modal-card-foot'>
-				<a class='button is-success' @click='create()'>
+				<a class='button is-success' @click='create()' v-if='!(($parent.currentSong === null || ($parent.currentSong.likes === -1 && $parent.currentSong.dislikes === -1)) && ($parent.previousSong === null || ($parent.previousSong.likes === -1 || $parent.previousSong.dislikes === -1)))'>
 					<i class='material-icons save-changes'>done</i>
 					<span>&nbsp;Create</span>
 				</a>
@@ -115,9 +116,7 @@
 						{ name: 'Duration', reasons: [] },
 						{ name: 'Artists', reasons: [] },
 						{ name: 'Thumbnail', reasons: [] }
-					],
-					createdBy: this.$parent.$parent.userId,
-					createdAt: Date.now()
+					]
 				},
 				issues: [
 					{

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

@@ -50,7 +50,6 @@
 			},
 			isNotSelected: function(id) {
 				let _this = this;
-				console.log(_this.$parent.station);
 				//TODO Also change this once it changes for a station
 				if (_this.$parent.station && _this.$parent.station.privatePlaylist === id) return false;
 				return true;
@@ -65,7 +64,7 @@
 					if (res.status == 'success') _this.playlists = res.data;
 				});
 				_this.socket.on('event:playlist.create', (playlist) => {
-						_this.playlists.push(playlist);
+					_this.playlists.push(playlist);
 				});
 				_this.socket.on('event:playlist.delete', (playlistId) => {
 					_this.playlists.forEach((playlist, index) => {
@@ -107,6 +106,7 @@
 <style type='scss' scoped>
 	.sidebar {
 		position: fixed;
+		z-index: 1;
 		top: 0;
 		right: 0;
 		width: 300px;

+ 3 - 2
frontend/components/Sidebars/Queue.vue

@@ -6,7 +6,7 @@
 			<article class="media">
 				<figure class="media-left">
 					<p class="image is-64x64">
-						<img :src="$parent.currentSong.thumbnail" onerror="this.src='/assets/notes.png'">
+						<img :src="$parent.currentSong.thumbnail" onerror="this.src='/assets/notes-transparent.png'">
 					</p>
 				</figure>
 				<div class="media-content">
@@ -51,7 +51,7 @@
 			io.getSocket((socket) => {
 				_this.socket = socket;
 				_this.socket.emit('stations.getPlaylist', _this.$parent.stationId, res => {
-						if (res.status == 'success') _this.playlist = res.data;
+					if (res.status == 'success') _this.playlist = res.data;
 				});
 			});
 		}
@@ -61,6 +61,7 @@
 <style type='scss' scoped>
 	.sidebar {
 		position: fixed;
+		z-index: 1;
 		top: 0;
 		right: 0;
 		width: 300px;

+ 1 - 0
frontend/components/Sidebars/UsersList.vue

@@ -15,6 +15,7 @@
 <style type='scss' scoped>
 	.sidebar {
 		position: fixed;
+		z-index: 1;
 		top: 0;
 		right: 0;
 		width: 300px;

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

@@ -4,14 +4,14 @@
 			<a class='nav-item logo' href='#' v-link='{ path: "/" }' @click='this.$dispatch("leaveStation", title)'>
 				Musare
 			</a>
-			<a v-if='$parent.$parent.loggedIn' class='nav-item' href='#' @click='$parent.modals.report = !$parent.modals.report'>
+			<a class='nav-item' href='#' v-if='isOwner()' @click='$parent.toggleModal("editStation")'>
 				<span class='icon'>
-					<i class='material-icons'>report</i>
+					<i class='material-icons'>settings</i>
 				</span>
 			</a>
-			<a class='nav-item' href='#' v-if='isOwner()' @click='$parent.toggleModal("editStation")'>
+			<a v-if='$parent.$parent.loggedIn' class='nav-item' href='#' @click='$parent.modals.report = !$parent.modals.report'>
 				<span class='icon'>
-					<i class='material-icons'>settings</i>
+					<i class='material-icons'>report</i>
 				</span>
 			</a>
 			<a v-if='isOwner()' class='nav-item' href='#' @click='$parent.skipStation()'>
@@ -37,9 +37,9 @@
 			</a>
 		</div>
 
-		<!--<div class='nav-center'>
-			{{title}}
-		</div>-->
+		<div class='nav-center stationDisplayName'>
+			{{$parent.station.displayName}}
+		</div>
 
 		<span class="nav-toggle" :class="{ 'is-active': isMobile }" @click="isMobile = !isMobile">
 			<span></span>

+ 10 - 10
frontend/components/Station/OfficialHeader.vue

@@ -14,6 +14,11 @@
 					<i class='material-icons'>queue_music</i>
 				</span>
 			</a>
+			<a v-if='$parent.$parent.loggedIn' class='nav-item' href='#' @click='$parent.modals.report = !$parent.modals.report'>
+				<span class='icon'>
+					<i class='material-icons'>report</i>
+				</span>
+			</a>
 			<a v-if='isOwner()' class='nav-item' href='#' @click='$parent.skipStation()'>
 				<span class='icon'>
 					<i class='material-icons'>skip_next</i>
@@ -37,9 +42,9 @@
 			</a>
 		</div>
 
-		<!--<div class='nav-center'>
-			{{title}}
-		</div>-->
+		<div class='nav-center stationDisplayName'>
+			{{$parent.station.displayName}}
+		</div>
 
 		<span class="nav-toggle" :class="{ 'is-active': isMobile }" @click="isMobile = !isMobile">
 			<span></span>
@@ -48,11 +53,6 @@
 		</span>
 
 		<div class="nav-right nav-menu" :class="{ 'is-active': isMobile }">
-			<a v-if='$parent.$parent.loggedIn' class='nav-item' href='#' @click='$parent.modals.report = !$parent.modals.report'>
-				<span class='icon'>
-					<i class='material-icons'>report</i>
-				</span>
-			</a>
 			<a class='nav-item' href='#' @click='$parent.sidebars.queue = !$parent.sidebars.queue' v-if='$parent.station.partyMode === true'>
 				<span class='icon'>
 					<i class='material-icons'>queue_music</i>
@@ -68,11 +68,11 @@
 					<i class='material-icons'>people</i>
 				</span>
 			</a>-->
-			<a class='nav-item' href='#' @click='$parent.sidebars.playlist = !$parent.sidebars.playlist' v-if='$parent.type === "community"'>
+			<!--a class='nav-item' href='#' @click='$parent.sidebars.playlist = !$parent.sidebars.playlist'>
 				<span class='icon'>
 					<i class='material-icons'>library_music</i>
 				</span>
-			</a>
+			</a-->
 		</div>
 	</nav>
 </template>

+ 33 - 57
frontend/components/Station/Station.vue

@@ -59,7 +59,7 @@
 						</div>
 					</div>
 					<div class="column is-4-desktop is-12-mobile" v-if="!simpleSong">
-						<img class="image" id="song-thumbnail" style="margin-top: 10px !important" :src="currentSong.thumbnail" alt="Song Thumbnail" onerror="this.src='/assets/notes.png'" />
+						<img class="image" id="song-thumbnail" style="margin-top: 10px !important" :src="currentSong.thumbnail" alt="Song Thumbnail" onerror="this.src='/assets/notes-transparent.png'" />
 					</div>
 				</div>
 			</div>
@@ -90,7 +90,7 @@
 				type: '',
 				playerReady: false,
 				previousSong: null,
-				currentSong: null,
+				currentSong: {},
 				player: undefined,
 				timePaused: 0,
 				paused: false,
@@ -131,7 +131,6 @@
 			},
 			youtubeReady: function() {
 				let local = this;
-				console.log("@@5", local.currentSong._id);
 				if (!local.player) {
 					local.player = new YT.Player("player", {
 						height: 270,
@@ -141,13 +140,11 @@
 						playerVars: {controls: 0, iv_load_policy: 3, rel: 0, showinfo: 0},
 						events: {
 							'onReady': function (event) {
-								console.log("@@6");
 								local.playerReady = true;
 								let volume = parseInt(localStorage.getItem("volume"));
 								volume = (typeof volume === "number") ? volume : 20;
 								local.player.setVolume(volume);
 								if (volume > 0) local.player.unMute();
-								console.log("@@7");
 								local.playVideo();
 							},
 							'onStateChange': function (event) {
@@ -175,9 +172,7 @@
 			},
 			playVideo: function() {
 				let local = this;
-				console.log("@@9");
 				if (local.playerReady) {
-					console.log("@@@1");
 					local.videoLoading = true;
 					local.player.loadVideoById(local.currentSong._id, local.getTimeElapsed() / 1000 + local.currentSong.skipDuration);
 
@@ -204,14 +199,12 @@
 				let currentTime = Date.now();
 
 				if (local.currentTime !== undefined && local.paused) {
-					console.log("123");
 					local.timePaused += (Date.now() - local.currentTime);
 					local.currentTime = undefined;
 				}
 
 				let duration = (Date.now() - local.startedAt - local.timePaused) / 1000;
 				let songDuration = local.currentSong.duration;
-				//console.log(duration, currentTime, local.startedAt, local.timePaused, local.startedAt - local.timePaused, Date.now() - local.startedAt - local.timePaused, Date.now() - local.startedAt);
 				if (songDuration <= duration) local.player.pauseVideo();
 				if ((!local.paused) && duration <= songDuration) local.timeElapsed = local.formatTime(duration);
 			},
@@ -243,68 +236,46 @@
 			skipStation: function () {
 				let _this = this;
 				_this.socket.emit('stations.forceSkip', _this.stationId, data => {
-					if (data.status !== 'success') {
-						Toast.methods.addToast(`Error: ${data.message}`, 8000);
-					} else {
-						Toast.methods.addToast('Successfully skipped the station\'s current song.', 4000);
-					}
+					if (data.status !== 'success') Toast.methods.addToast(`Error: ${data.message}`, 8000);
+					else Toast.methods.addToast('Successfully skipped the station\'s current song.', 4000);
 				});
 			},
 			voteSkipStation: function () {
 				let _this = this;
 				_this.socket.emit('stations.voteSkip', _this.stationId, data => {
-					if (data.status !== 'success') {
-						Toast.methods.addToast(`Error: ${data.message}`, 8000);
-					} else {
-						Toast.methods.addToast('Successfully voted to skip the current song.', 4000);
-					}
+					if (data.status !== 'success') Toast.methods.addToast(`Error: ${data.message}`, 8000);
+					else Toast.methods.addToast('Successfully voted to skip the current song.', 4000);
 				});
 			},
 			resumeStation: function () {
 				let _this = this;
 				_this.socket.emit('stations.resume', _this.stationId, data => {
-					console.log(data);
-					if (data.status !== 'success') {
-						Toast.methods.addToast(`Error: ${data.message}`, 8000);
-					} else {
-						Toast.methods.addToast('Successfully resumed the station.', 4000);
-					}
+					if (data.status !== 'success') Toast.methods.addToast(`Error: ${data.message}`, 8000);
+					else Toast.methods.addToast('Successfully resumed the station.', 4000);
 				});
 			},
 			pauseStation: function () {
 				let _this = this;
 				_this.socket.emit('stations.pause', _this.stationId, data => {
-					console.log(data);
-					if (data.status !== 'success') {
-						Toast.methods.addToast(`Error: ${data.message}`, 8000);
-					} else {
-						Toast.methods.addToast('Successfully paused the station.', 4000);
-					}
+					if (data.status !== 'success') Toast.methods.addToast(`Error: ${data.message}`, 8000);
+					else Toast.methods.addToast('Successfully paused the station.', 4000);
 				});
 			},
 			toggleLike: function() {
 				let _this = this;
 				if (_this.liked) _this.socket.emit('songs.unlike', _this.currentSong._id, data => {
-					if (data.status !== 'success') {
-						Toast.methods.addToast(`Error: ${data.message}`, 8000);
-					}
+					if (data.status !== 'success') Toast.methods.addToast(`Error: ${data.message}`, 8000);
 				}); else _this.socket.emit('songs.like', _this.currentSong._id, data => {
-					if (data.status !== 'success') {
-						Toast.methods.addToast(`Error: ${data.message}`, 8000);
-					}
+					if (data.status !== 'success') Toast.methods.addToast(`Error: ${data.message}`, 8000);
 				});
 			},
 			toggleDislike: function() {
 				let _this = this;
 				if (_this.disliked) return _this.socket.emit('songs.undislike', _this.currentSong._id, data => {
-					if (data.status !== 'success') {
-						Toast.methods.addToast(`Error: ${data.message}`, 8000);
-					}
+					if (data.status !== 'success') Toast.methods.addToast(`Error: ${data.message}`, 8000);
 				});
 				_this.socket.emit('songs.dislike', _this.currentSong._id, data => {
-					if (data.status !== 'success') {
-						Toast.methods.addToast(`Error: ${data.message}`, 8000);
-					}
+					if (data.status !== 'success') Toast.methods.addToast(`Error: ${data.message}`, 8000);
 				});
 			},
 			joinStation: function () {
@@ -319,7 +290,7 @@
 							owner: res.data.owner,
 							privatePlaylist: res.data.privatePlaylist
 						};
-						_this.currentSong = (res.data.currentSong) ? res.data.currentSong : null;
+						_this.currentSong = (res.data.currentSong) ? res.data.currentSong : {};
 						_this.type = res.data.type;
 						_this.startedAt = res.data.startedAt;
 						_this.paused = res.data.paused;
@@ -330,7 +301,6 @@
 							if (_this.simpleSong) {
 								_this.currentSong.skipDuration = 0;
 							}
-							console.log(12334);
 							_this.youtubeReady();
 							_this.playVideo();
 							_this.socket.emit('songs.getOwnSongRatings', res.data.currentSong._id, data => {
@@ -341,15 +311,11 @@
 							});
 						} else {
 							if (_this.playerReady) _this.player.pauseVideo();
-							console.log("NO SONG TRUE1", res.data);
 							_this.noSong = true;
 						}
 						if (_this.type === 'community') {
 							_this.socket.emit('stations.getQueue', _this.stationId, data => {
-								console.log(data);
-								if (data.status === 'success') {
-									_this.queue = data.queue;
-								}
+								if (data.status === 'success') _this.queue = data.queue;
 							});
 						}
 					} else {
@@ -376,7 +342,7 @@
 				});
 
 				_this.socket.on('event:songs.next', data => {
-					_this.previousSong = _this.currentSong;
+					_this.previousSong = (_this.currentSong._id) ? _this.currentSong : null;
 					_this.currentSong = (data.currentSong) ? data.currentSong : {};
 					_this.startedAt = data.startedAt;
 					_this.paused = data.paused;
@@ -387,7 +353,6 @@
 						if (_this.simpleSong) {
 							_this.currentSong.skipDuration = 0;
 						}
-						console.log(1233, _this.stationId);
 						if (!_this.playerReady) _this.youtubeReady();
 						else _this.playVideo();
 						_this.socket.emit('songs.getOwnSongRatings', data.currentSong._id, (data) => {
@@ -398,7 +363,6 @@
 						});
 					} else {
 						if (_this.playerReady) _this.player.pauseVideo();
-						console.log("NO SONG TRUE2", data);
 						_this.noSong = true;
 					}
 				});
@@ -452,14 +416,22 @@
 				});
 
 				_this.socket.on('event:queue.update', queue => {
+					if (this.type === 'community') this.queue = queue;
+				});
+
+				_this.socket.on('event:song.voteSkipSong', () => {
+					if (this.currentSong) this.currentSong.skipVotes++;
+				});
+
+				_this.socket.on('event:privatePlaylist.selected', (playlistId) => {
 					if (this.type === 'community') {
-						this.queue = queue;
+						this.station.privatePlaylist = playlistId;
 					}
 				});
 
-				_this.socket.on('event:song.voteSkipSong', () => {
-					if (this.currentSong) {
-						this.currentSong.skipVotes++;
+				_this.socket.on('event:partyMode.updated', (partyMode) => {
+					if (this.type === 'community') {
+						this.station.partyMode = partyMode;
 					}
 				});
 			});
@@ -489,6 +461,10 @@
 		text-align: center;
 	}
 
+	.stationDisplayName {
+		color: white !important;
+	}
+
 	.slideout {
 		top: 50px;
 		height: 100%;

+ 2 - 1
frontend/components/User/Settings.vue

@@ -13,7 +13,7 @@
 			</p>
 		</div>
 		<label class="label">Email</label>
-		<div class="control is-grouped">
+		<div class="control is-grouped" v-if="user.email">
 			<p class="control is-expanded has-icon has-icon-right">
 				<input class="input" type="text" placeholder="Change email address" v-model="user.email.address">
 				<!--Remove validation if it's their own without changing-->
@@ -33,6 +33,7 @@
 	import MainFooter from '../MainFooter.vue';
 
 	import LoginModal from '../Modals/Login.vue'
+	import io from '../../io'
 
 	export default {
 		data() {

+ 2 - 1
frontend/components/User/Show.vue

@@ -2,7 +2,7 @@
 	<div v-if="isUser">
 		<main-header></main-header>
 		<div class="container">
-			<img class="avatar" src="https://avatars2.githubusercontent.com/u/11198912?v=3&s=460"/>
+			<img class="avatar" src="/assets/notes.png"/>
 			<h2 class="has-text-centered username">@{{user.username}}</h2>
 			<div class="admin-functionality" v-if="user.role == 'admin'">
 				<a class="button is-small is-info is-outlined" @click="changeRank('admin')" v-if="user.role == 'default'">Promote to Admin</a>
@@ -36,6 +36,7 @@
 
 	import MainHeader from '../MainHeader.vue';
 	import MainFooter from '../MainFooter.vue';
+	import io from '../../io';
 
 	export default {
 		data() {

+ 18 - 20
frontend/components/pages/Home.vue

@@ -6,7 +6,7 @@
 			<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-image">
 					<figure class="image is-square">
-						<img :src="station.currentSong.thumbnail" onerror="this.src='/assets/notes.png'" />
+						<img :src="station.currentSong.thumbnail" onerror="this.src='/assets/notes-transparent.png'" />
 					</figure>
 				</div>
 				<div class="card-content">
@@ -31,7 +31,7 @@
 			<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-image">
 					<figure class="image is-square">
-						<img :src="station.currentSong.thumbnail" onerror="this.src='/assets/notes.png'" />
+						<img :src="station.currentSong.thumbnail" onerror="this.src='/assets/notes-transparent.png'" />
 					</figure>
 				</div>
 				<div class="card-content">
@@ -87,8 +87,7 @@
 						_this.init();
 					});
 					_this.socket.on('event:stations.created', station => {
-						console.log("CREATED!!!", station);
-						if (!station.currentSong) station.currentSong = {thumbnail: '/assets/notes.png'};
+						if (!station.currentSong) station.currentSong = {thumbnail: '/assets/notes-transparent.png'};
 						if (station.privacy !== 'public') {
 							station.class = {'station-red': true}
 						} else if (station.type === 'community') {
@@ -107,27 +106,26 @@
 			},
 			init: function() {
 				let _this = this;
-				_this.socket.emit("stations.index", data => {
-					_this.stations.community = [];
-					_this.stations.official = [];
-					if (data.status === "success")  data.stations.forEach(station => {
-						if (!station.currentSong) station.currentSong = { thumbnail: '/assets/notes.png' };
-						console.log(station.privacy);
-						if (station.privacy !== 'public') {
-							console.log(123);
-							station.class = {'station-red': true}
-						} else if (station.type === 'community') {
-							auth.getStatus((authenticated, role, username, userId) => {
+				auth.getStatus((authenticated, role, username, userId) => {
+					_this.socket.emit("stations.index", data => {
+						_this.stations.community = [];
+						_this.stations.official = [];
+						if (data.status === "success")  data.stations.forEach(station => {
+							if (!station.currentSong) 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.type == 'official') _this.stations.official.push(station);
-						else _this.stations.community.push(station);
+							}
+							if (station.type == 'official') _this.stations.official.push(station);
+							else _this.stations.community.push(station);
+						});
+					});
+					_this.socket.emit("apis.joinRoom", 'home', () => {
 					});
 				});
-				_this.socket.emit("apis.joinRoom", 'home', () => {});
 			}
 		},
 		components: { MainHeader, MainFooter }

+ 11 - 6
frontend/io.js

@@ -1,6 +1,7 @@
 let callbacks = [];
 let onConnectCallbacks = [];
 let onDisconnectCallbacks = [];
+let onConnectErrorCallbacks = [];
 
 export default {
 
@@ -12,15 +13,19 @@ export default {
 		else callbacks.push(cb);
 	},
 
-	onConnect: function(cb) {
+	onConnect: (cb) => {
 		onConnectCallbacks.push(cb);
 	},
 
-	onDisconnect: function(cb) {
+	onDisconnect: (cb) => {
 		onDisconnectCallbacks.push(cb);
 	},
 
-	removeAllListeners: function() {
+	onConnectError: (cb) => {
+		onConnectErrorCallbacks.push(cb);
+	},
+
+	removeAllListeners: function () {
 		Object.keys(this.socket._callbacks).forEach((id) => {
 			if (id.indexOf("$event:song") !== -1) {
 				delete this.socket._callbacks[id];
@@ -32,21 +37,21 @@ export default {
 		this.socket = window.socket = io(url);
 		this.socket.on('connect', () => {
 			// Connect
-			console.log("SOCKET.IO CONNECTED");
 			onConnectCallbacks.forEach((cb) => {
 				cb();
 			});
 		});
 		this.socket.on('disconnect', () => {
 			// Disconnect
-			console.log("SOCKET.IO DISCONNECTED");
 			onDisconnectCallbacks.forEach((cb) => {
 				cb();
 			});
 		});
 		this.socket.on('connect_error', () => {
 			// Connect error
-			console.log("SOCKET.IO ERROR WHILE CONNECTING");
+			onConnectErrorCallbacks.forEach((cb) => {
+				cb();
+			});
 		});
 		this.ready = true;
 		callbacks.forEach(callback => {

+ 1 - 1
frontend/package.json

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

部分文件因文件數量過多而無法顯示