Quellcode durchsuchen

Added Ratings and Time station component, added station ducks, converted a lot of the code to use the ducks.

KrisVos130 vor 7 Jahren
Ursprung
Commit
a6a9aef6df

+ 5 - 4
frontend/app/js/app.jsx

@@ -4,10 +4,9 @@ import PropTypes from "prop-types";
 import { connect } from "react-redux";
 import { bindActionCreators } from "redux";
 import { actionCreators as volumeActionCreators } from "ducks/volume";
+import { actionCreators as sessionActionCreators } from "ducks/session";
 import { translate } from "react-i18next";
 
-import { ban, authenticate } from "actions/auth";
-import { initialize as initializeVolume } from "actions/volume";
 import Navbar from "components/Global/Navbar";
 
 import config from "config";
@@ -20,6 +19,8 @@ import { asyncComponent } from "react-async-component";
 	onVolumeLoudnessChange: bindActionCreators(volumeActionCreators.changeVolumeLoudness, dispatch),
 	onVolumeMute: bindActionCreators(volumeActionCreators.muteVolume, dispatch),
 	onVolumeUnmute: bindActionCreators(volumeActionCreators.unmuteVolume, dispatch),
+	onLogin: bindActionCreators(sessionActionCreators.login, dispatch),
+	onBanned: bindActionCreators(sessionActionCreators.banned, dispatch),
 }))
 @translate(["pages"], { wait: false })
 class App extends Component { // eslint-disable-line react/no-multi-comp
@@ -39,9 +40,9 @@ class App extends Component { // eslint-disable-line react/no-multi-comp
 		io.init(config.serverDomain);
 		io.getSocket(socket => {
 			socket.on("ready", (loggedIn, role, username, userId) => {
-				dispatch(authenticate({ loggedIn, role, username, userId }));
+				this.props.onLogin(userId, username, role);
 			});
-			socket.on("keep.event:banned", reason => dispatch(ban(reason)));
+			socket.on("keep.event:banned", reason => this.props.banned(reason));
 
 			socket.on("keep.event:user.session.removed", () => {
 				location.reload();

+ 40 - 19
frontend/app/js/components/AuthRoute.jsx

@@ -5,6 +5,9 @@ import { Redirect, Route } from "react-router-dom";
 import { translate } from "react-i18next";
 import { initializeStation } from "actions/station";
 
+import { actionCreators as stationInfoActionCreators } from "ducks/stationInfo";
+import { bindActionCreators } from "redux";
+
 import io from "io";
 
 const renderMergedProps = (component, ...rest) => {
@@ -28,23 +31,30 @@ function clone(obj) {
 }
 
 @connect(state => ({
-	loggedIn: state.user.get("loggedIn"),
-	role: state.user.get("role"),
-	authProcessed: state.user.get("authProcessed"),
+	user: {
+		role: state.session.get("role"),
+		loggedIn: state.session.get("loggedIn"),
+		authProcessed: state.session.get("authProcessed"),
+	},
 	station: {
-		stationId: state.station.get("id"),
-		name: state.station.get("name"),
+		stationId: state.station.info.get("stationId"),
+		name: state.station.info.get("name"),
 	},
+}),
+(dispatch) => ({
+	onJoinStation: bindActionCreators(stationInfoActionCreators.joinStation, dispatch),
 }))
 
 @translate(["general"], { wait: true })
 export default class AuthRoute extends Component {
 	static propTypes = {
-		loggedIn: PropTypes.bool,
+		user: PropTypes.shape({
+			role: PropTypes.string,
+			loggedIn: PropTypes.bool,
+			authProcessed: PropTypes.bool,
+		}),
 		title: PropTypes.string,
-		role: PropTypes.string,
 		auth: PropTypes.string,
-		authProcessed: PropTypes.bool,
 		component: PropTypes.oneOfType([
 			PropTypes.element,
 			PropTypes.func,
@@ -54,11 +64,13 @@ export default class AuthRoute extends Component {
 	};
 
 	static defaultProps = {
-		loggedIn: false,
+		user: {
+			role: "default",
+			loggedIn: false,
+			authProcessed: false,
+		},
 		title: "Musare",
-		role: "default",
 		auth: "ignored",
-		authProcessed: false,
 		component: () => {},
 		computedMatch: {},
 		t: () => {},
@@ -92,23 +104,31 @@ export default class AuthRoute extends Component {
 
 	getStationData = () => {
 		io.getSocket(socket => {
-			socket.emit("stations.findByName", this.state.stationName, res => {
+			socket.emit("stations.findByName", this.state.stationName, res => { //TODO Add simple endpoint
 				if (res.status === "success") {
-					this.props.dispatch(initializeStation({
+					this.props.onJoinStation({
 						//TODO Refactor this to be better optimized
 						stationId: res.data._id,
 						name: res.data.name,
 						displayName: res.data.displayName,
 						description: res.data.description,
 						privacy: res.data.privacy,
-						locked: res.data.locked,
-						partyMode: res.data.partyMode,
-						owner: res.data.owner,
-						privatePlaylist: res.data.privatePlaylist,
 						type: res.data.type,
+						ownerId: res.data.owner,
 						paused: res.data.paused,
 						pausedAt: res.data.pausedAt,
-					}));
+						// Mode
+						// Userlist
+							userList: [],
+							userCount: 0,
+							locked: res.data.locked,
+							partyMode: res.data.partyMode,
+						// Usercount
+						songList: res.data.queue,
+						// locked: res.data.locked,
+						// partyMode: res.data.partyMode,
+						// privatePlaylist: res.data.privatePlaylist,
+					});
 				} else {
 					this.setState({
 						noStation: true,
@@ -119,7 +139,8 @@ export default class AuthRoute extends Component {
 	};
 
 	render() {
-		const { auth, role, loggedIn, authProcessed, t } = this.props;
+		const { user, auth, t } = this.props;
+		const { loggedIn, role, authProcessed } = user;
 		const { waitingFor, receivedStationData } = this.state;
 
 		if (this.state.continue) {

+ 7 - 5
frontend/app/js/components/Global/Navbar.jsx

@@ -5,7 +5,9 @@ import { NavLink } from "react-router-dom";
 import { translate } from "react-i18next";
 
 @connect(state => ({
-	loggedIn: state.user.get("loggedIn"),
+	user: {
+		loggedIn: state.session.get("loggedIn")
+	},
 }))
 @translate(["navbar"], { wait: true })
 export default class Menu extends Component {
@@ -56,10 +58,10 @@ export default class Menu extends Component {
 				</button>
 				<navbar className={ (this.state.showNavbar) ? "show" : "" }>
 					{this.getLink("/", t("navbar:home"))}
-					{this.getLink("/login", t("navbar:login"), !this.props.loggedIn)}
-					{this.getLink("/register", t("navbar:register"), !this.props.loggedIn)}
-					{this.getLink("/settings", t("navbar:settings"), this.props.loggedIn)}
-					{this.getLink("/logout", t("navbar:logout"), this.props.loggedIn)}
+					{this.getLink("/login", t("navbar:login"), !this.props.user.loggedIn)}
+					{this.getLink("/register", t("navbar:register"), !this.props.user.loggedIn)}
+					{this.getLink("/settings", t("navbar:settings"), this.props.user.loggedIn)}
+					{this.getLink("/logout", t("navbar:logout"), this.props.user.loggedIn)}
 				</navbar>
 			</header>
 		);

+ 4 - 0
frontend/app/js/ducks/session.js

@@ -38,6 +38,7 @@ const initialState = Map({
 	userId: "",
 	username: "",
 	role: "default",
+	authProcessed: false,
 	banned: {
 		status: false,
 		reason: "",
@@ -52,13 +53,16 @@ function reducer(state = initialState, action) {
 			userId: initialState.get("userId"),
 			username: initialState.get("username"),
 			role: initialState.get("role"),
+			authProcessed: false,
 		});
 	case LOGIN:
 		const { userId, username, role } = action;
 		return state.merge({
+			loggedIn: true,
 			userId,
 			username,
 			role,
+			authProcessed: true,
 		});
 	case BANNED:
 		const { reason } = action;

+ 163 - 0
frontend/app/js/ducks/stationCurrentSong.js

@@ -0,0 +1,163 @@
+import { Map, List } from "immutable";
+
+const NEXT_SONG = "STATION_CURRENT_SONG::NEXT_SONG";
+const LIKE_UPDATE = "STATION_CURRENT_SONG::LIKE_UPDATE";
+const DISLIKE_UPDATE = "STATION_CURRENT_SONG::DISLIKE_UPDATE";
+const LIKED_UPDATE = "STATION_CURRENT_SONG::LIKED_UPDATE";
+const DISLIKED_UPDATE = "STATION_CURRENT_SONG::DISLIKED_UPDATE";
+const PAUSE_TIME = "STATION_CURRENT_SONG::PAUSE_TIME";
+const RESUME_TIME = "STATION_CURRENT_SONG::RESUME_TIME";
+
+function nextSong(song) {
+	return {
+		type: NEXT_SONG,
+		song,
+	}
+}
+
+function likeUpdate(likes) {
+	return {
+		type: LIKE_UPDATE,
+		likes,
+	}
+}
+
+function dislikeUpdate(dislikes) {
+	return {
+		type: DISLIKE_UPDATE,
+		dislikes,
+	}
+}
+
+function likedUpdate(liked) {
+	return {
+		type: LIKED_UPDATE,
+		liked,
+	}
+}
+
+function dislikedUpdate(disliked) {
+	return {
+		type: DISLIKED_UPDATE,
+		disliked,
+	}
+}
+
+function pauseTime(pausedAt) {
+	return {
+		type: PAUSE_TIME,
+		pausedAt,
+	}
+}
+
+function resumeTime(timePaused) {
+	return {
+		type: PAUSE_TIME,
+		timePaused,
+	}
+}
+
+
+
+const initialState = Map({
+	"songId": "",
+	"timings": Map({
+		"duration": 0,
+		"skipDuration": 0,
+		"timeElapsed": 0,
+		"timePaused": 0,
+		"pausedAt": 0,
+		"startedAt": 0,
+	}),
+	"title": "",
+	"artists": [],
+	"ratings": Map({
+		"enabled": false,
+		"likes": 0,
+		"dislikes": 0,
+		"liked": false,
+		"disliked": false,
+	}),
+});
+
+function reducer(state = initialState, action) {
+	switch (action.type) {
+	case NEXT_SONG:
+		const { song } = action;
+		if (song === null) return initialState;
+		//TODO Handle no song event / Song being null event (so no song)
+		state = initialState;
+
+		return state.merge({
+			songId: song.songId,
+			timings: Map({
+				duration: song.timings.duration,
+				skipDuration: song.timings.skipDuration,
+				timeElapsed: 0,
+				timePaused: song.timings.timePaused,
+				startedAt: song.timings.startedAt,
+			}),
+			title: song.title,
+			artists: List(song.artists),
+			ratings: Map({
+				enabled: !(song.ratings.likes === -1 && song.ratings.dislikes === -1),
+				likes: song.ratings.likes,
+				dislikes: song.ratings.dislikes,
+				liked: false,
+				disliked: false,
+			}),
+		});
+	case LIKE_UPDATE:
+		const { likes } = action;
+		state = state.setIn(["ratings", "likes"], likes);
+		return state;
+	case DISLIKE_UPDATE:
+		const { dislikes } = action;
+		state = state.setIn(["ratings", "dislikes"], dislikes);
+		return state;
+	case LIKED_UPDATE:
+		const { liked } = action;
+		state = state.setIn(["ratings", "liked"], liked);
+		return state;
+	case DISLIKED_UPDATE:
+		const { disliked } = action;
+		state = state.setIn(["ratings", "disliked"], disliked);
+		return state;
+	case PAUSE_TIME:
+		const { pausedAt } = action;
+		state = state.setIn(["timings", "pausedAt"], pausedAt);
+		return state;
+	case RESUME_TIME:
+		const { timePaused } = action;
+		state = state.setIn(["timings", "timePaused"], timePaused);
+		return state;
+	}
+	return state;
+}
+
+const actionCreators = {
+	nextSong,
+	likeUpdate,
+	dislikeUpdate,
+	likedUpdate,
+	dislikedUpdate,
+	pauseTime,
+	resumeTime,
+};
+
+const actionTypes = {
+	NEXT_SONG,
+	LIKE_UPDATE,
+	DISLIKE_UPDATE,
+	LIKED_UPDATE,
+	DISLIKED_UPDATE,
+	PAUSE_TIME,
+	RESUME_TIME,
+};
+
+export {
+	actionCreators,
+	actionTypes,
+};
+
+export default reducer;

+ 235 - 0
frontend/app/js/ducks/stationInfo.js

@@ -0,0 +1,235 @@
+import { Map, List } from "immutable";
+
+const JOIN_STATION = "STATION_INFO::JOIN_STATION";
+const LEAVE_STATION = "STATION_INFO::LEAVE_STATION";
+const USER_LIST_UPDATE = "STATION_INFO::USER_LIST_UPDATE";
+const USER_COUNT_UPDATE = "STATION_INFO::USER_COUNT_UPDATE";
+const NAME_UPDATE = "STATION_INFO::NAME_UPDATE";
+const DISPLAY_NAME_UPDATE = "STATION_INFO::DISPLAY_NAME_UPDATE";
+const DESCRIPTION_UPDATE = "STATION_INFO::DESCRIPTION_UPDATE";
+const MODE_UPDATE = "STATION_INFO::MODE_UPDATE";
+const QUEUE_INDEX = "STATION_INFO::QUEUE_INDEX";
+const QUEUE_UPDATE = "STATION_INFO::QUEUE_UPDATE";
+
+function joinStation(station) {
+	return {
+		type: JOIN_STATION,
+		station,
+	}
+}
+
+function leaveStation() {
+	return {
+		type: LEAVE_STATION,
+	}
+}
+
+function userListUpdate(userList) {
+	return {
+		type: USER_LIST_UPDATE,
+		userList,
+	}
+}
+
+function userCountUpdate(userCount) {
+	return {
+		type: USER_COUNT_UPDATE,
+		userCount,
+	}
+}
+
+function nameUpdate(name) {
+	return {
+		type: NAME_UPDATE,
+		name,
+	}
+}
+
+function displayNameUpdate(displayName) {
+	return {
+		type: DISPLAY_NAME_UPDATE,
+		displayName,
+	}
+}
+
+function descriptionUpdate(description) {
+	return {
+		type: DESCRIPTION_UPDATE,
+		description,
+	}
+}
+
+function modeUpdate(mode) {
+	return {
+		type: MODE_UPDATE,
+		mode,
+	}
+}
+
+function queueIndex(songList) {
+	return {
+		type: QUEUE_INDEX,
+		songList,
+	}
+}
+
+function queueUpdate(songList) {
+	return {
+		type: QUEUE_UPDATE,
+		songList,
+	}
+}
+
+
+
+const initialState = Map({
+	"stationId": "",
+	"name": "",
+	"displayName": "",
+	"description": "",
+	"privacy": "private",
+	"type": "community",
+	"ownerId": "",
+	"paused": true,
+	"mode": "",
+	"userList": List([]),
+	"userCount": 0,
+	"songList": List([]),
+});
+
+function reducer(state = initialState, action) {
+	let name, displayName, description, mode, userList, userCount, songList;
+
+	function getModeTemp(partyEnabled, queueLocked) {
+		// If party enabled
+		// If queue locked
+		// Mode is DJ
+		// If queue not locked
+		// Mode party
+		// If party not enabled
+		// Mode is normal
+
+		if (partyEnabled) {
+			if (queueLocked) return "dj";
+			else return "party";
+		} else return "normal";
+	}
+
+	switch (action.type) {
+	case JOIN_STATION:
+		const { stationId, privacy, type, ownerId, paused } = action.station;
+		name = action.station.name;
+		displayName = action.station.displayName;
+		description = action.station.description;
+		userCount = action.station.userCount;
+		mode = (getModeTemp(action.station.partyMode, action.station.locked));
+
+		userList = List([]);
+		action.station.userList.forEach((user) => {
+			userList.push(user);
+		});
+
+		songList = List([]);
+		action.station.songList.forEach((song) => {
+			songList.push(song);
+		});
+
+		return state.merge({
+			stationId,
+			name: action.station.name,
+			displayName,
+			description,
+			privacy,
+			type,
+			ownerId,
+			paused,
+			mode,
+			userList,
+			userCount,
+			songList,
+		});
+	case LEAVE_STATION:
+		return initialState;
+	case USER_LIST_UPDATE:
+		userList = List([]);
+		action.userList.forEach((user) => {
+			userList.push(user);
+		});
+
+		state.set("userList", userList);
+		return state;
+	case USER_COUNT_UPDATE:
+		userCount = action.userCount;
+
+		state.set("userCount", userCount);
+		return state;
+	case NAME_UPDATE:
+		name = action.name;
+
+		state.set("name", name);
+		return state;
+	case DISPLAY_NAME_UPDATE:
+		displayName = action.displayName;
+
+		state.set("displayName", displayName);
+		return state;
+	case DESCRIPTION_UPDATE:
+		description = action.description;
+
+		state.set("description", description);
+		return state;
+	case MODE_UPDATE:
+		mode = action.mode;
+
+		state.set("mode", mode);
+		return state;
+	case QUEUE_INDEX:
+		songList = List([]);
+		action.songList.forEach((song) => {
+			songList.push(song);
+		});
+
+		return state;
+	case QUEUE_UPDATE:
+		songList = List([]);
+		action.songList.forEach((song) => {
+			songList.push(song);
+		});
+
+		return state;
+	}
+	return state;
+}
+
+const actionCreators = {
+	joinStation,
+	leaveStation,
+	userListUpdate,
+	userCountUpdate,
+	nameUpdate,
+	displayNameUpdate,
+	descriptionUpdate,
+	modeUpdate,
+	queueIndex,
+	queueUpdate,
+};
+
+const actionTypes = {
+	JOIN_STATION,
+	LEAVE_STATION,
+	USER_LIST_UPDATE,
+	USER_COUNT_UPDATE,
+	NAME_UPDATE,
+	DISPLAY_NAME_UPDATE,
+	DESCRIPTION_UPDATE,
+	MODE_UPDATE,
+	QUEUE_INDEX,
+	QUEUE_UPDATE,
+};
+
+export {
+	actionCreators,
+	actionTypes,
+};
+
+export default reducer;

+ 16 - 8
frontend/app/js/reducers/index.js

@@ -1,19 +1,27 @@
 import { combineReducers } from "redux";
-import user from "reducers/user";
+//import user from "reducers/user";
 //import volume from "reducers/volume";
-import songPlayer from "reducers/songPlayer";
-import station from "reducers/station";
+//import songPlayer from "reducers/songPlayer";
+//import station from "reducers/station";
 import stationOverlay from "reducers/stationOverlay";
-import playlistQueue from "reducers/playlistQueue";
+//import playlistQueue from "reducers/playlistQueue";
 import homepage from "../ducks/homepage";
+import session from "../ducks/session";
 import volume from "../ducks/volume";
+import stationCurrentSong from "../ducks/stationCurrentSong";
+import stationInfo from "../ducks/stationInfo";
 
 export default combineReducers({
 	volume,
 	homepage,
-	user,
-	songPlayer,
-	station,
+	session,
+	//user,
+	//songPlayer,
+	//station,
 	stationOverlay,
-	playlistQueue,
+	//playlistQueue,
+	station: combineReducers({
+		info: stationInfo,
+		currentSong: stationCurrentSong,
+	}),
 });

+ 3 - 3
frontend/app/js/views/Home/index.jsx

@@ -17,10 +17,10 @@ import config from "config";
 
 @connect(state => ({
 	user: {
-		userId: state.user.get("userId"),
-		role: state.user.get("role"),
+		loggedIn: state.session.get("loggedIn"),
+		userId: state.session.get("userId"),
+		role: state.session.get("role"),
 	},
-	loggedIn: state.user.get("loggedIn"),
 	officialStations: state.homepage.getIn(["stations", "official"]),
 	communityStations: state.homepage.getIn(["stations", "community"]),
 }),

+ 8 - 8
frontend/app/js/views/Station/Player.jsx

@@ -10,15 +10,15 @@ const t = i18next.t;
 let getPlayerCallbacks = [];
 
 @connect(state => ({
-	volume: state.volume.get("volume"),
+	volume: state.volume.get("loudness"),
 	muted: state.volume.get("muted"),
-	songId: state.songPlayer.get("songId"),
-	startedAt: state.songPlayer.get("startedAt"),
-	timePaused: state.songPlayer.get("timePaused"),
-	skipDuration: state.songPlayer.get("skipDuration"),
-	pausedAt: state.songPlayer.get("pausedAt"),
-	exists: state.songPlayer.get("exists"),
-	paused: state.station.get("paused"),
+	songId: state.station.currentSong.get("songId"),
+	startedAt: state.station.currentSong.getIn(["timings", "startedAt"]),
+	timePaused: state.station.currentSong.getIn(["timings", "timePaused"]),
+	skipDuration: state.station.currentSong.getIn(["timings", "skipDuration"]),
+	pausedAt: state.station.currentSong.getIn(["timings", "pausedAt"]),
+	exists: state.station.currentSong.get("songId") !== "",
+	paused: state.station.info.get("paused"),
 }))
 export default class Player extends Component {
 	static propTypes = {

+ 77 - 0
frontend/app/js/views/Station/Ratings.jsx

@@ -0,0 +1,77 @@
+import React, { Component } from "react";
+import PropTypes from "prop-types";
+
+import { connect } from "react-redux";
+
+import io from "io";
+
+@connect(state => ({
+	user: {
+		loggedIn: state.session.get("loggedIn"),
+	},
+	likes: state.station.currentSong.getIn(["ratings", "likes"]),
+	dislikes: state.station.currentSong.getIn(["ratings", "dislikes"]),
+	liked: state.station.currentSong.getIn(["ratings", "liked"]),
+	disliked: state.station.currentSong.getIn(["ratings", "disliked"]),
+	ratingsEnabled: state.station.currentSong.getIn(["ratings", "enabled"]),
+	songId: state.station.currentSong.get("songId"),
+}))
+export default class Ratings extends Component {
+	constructor(props) {
+		super(props);
+	}
+
+	like = () => {
+		io.getSocket(socket => {
+			socket.emit("songs.like", this.props.songId, data => {/*TODO Handle error/success*/});
+		});
+	};
+
+	dislike = () => {
+		io.getSocket(socket => {
+			socket.emit("songs.dislike", this.props.songId, data => {/*TODO Handle error/success*/});
+		});
+	};
+
+	unlike = () => {
+		io.getSocket(socket => {
+			socket.emit("songs.unlike", this.props.songId, data => {/*TODO Handle error/success*/});
+		});
+	};
+
+	undislike = () => {
+		io.getSocket(socket => {
+			socket.emit("songs.undislike", this.props.songId, data => {/*TODO Handle error/success*/});
+		});
+	};
+
+	render() {
+		if (!this.props.ratingsEnabled) return null;
+
+		const likes = <span>{ this.props.likes }</span>;
+		const dislikes = <span>{ this.props.dislikes }</span>;
+		let likeButton = <i className="material-icons disabled">thumb_up</i>;
+		let dislikeButton = <i className="material-icons disabled">thumb_down</i>;
+
+		if (this.props.user.loggedIn) {
+			if (this.props.liked) likeButton = <i className="material-icons liked" onClick={ this.unlike }>thumb_up</i>;
+			else likeButton = <i className="material-icons" onClick={ this.like }>thumb_up</i>;
+
+			if (this.props.disliked) dislikeButton = <i className="material-icons disliked" onClick={ this.undislike }>thumb_down</i>;
+			else dislikeButton = <i className="material-icons" onClick={ this.dislike }>thumb_down</i>;
+		}
+
+		return (
+			<div className="ratings-container">
+				<div>
+					{ likeButton }
+					{ likes }
+				</div>
+				<div>
+					{ dislikeButton }
+					{ dislikes }
+				</div>
+			</div>
+		);
+	}
+}

+ 5 - 5
frontend/app/js/views/Station/Seekerbar.jsx

@@ -4,9 +4,9 @@ import PropTypes from "prop-types";
 import { connect } from "react-redux";
 
 @connect(state => ({
-	timeElapsed: state.songPlayer.get("timeElapsed") * 1000,
-	timeTotal: state.songPlayer.get("duration") * 1000,
-	paused: state.station.get("paused"),
+	timeElapsed: state.station.currentSong.getIn(["timings", "timeElapsed"]) * 1000,
+	timeTotal: state.station.currentSong.getIn(["timings", "duration"]) * 1000,
+	paused: state.station.info.get("paused"),
 }))
 export default class Seekerbar extends Component {
 	static propTypes = {
@@ -25,7 +25,7 @@ export default class Seekerbar extends Component {
 			percentage: 0,
 		};
 
-		setInterval(() => {
+		/*setInterval(() => {
 			if (!this.props.paused) {
 				let timeElapsedGuess = this.state.timeElapsedGuess;
 				timeElapsedGuess += 15;
@@ -43,7 +43,7 @@ export default class Seekerbar extends Component {
 					percentage: (this.props.timeElapsed / this.props.timeTotal) * 100,
 				});
 			}
-		}, 50);
+		}, 50);*/
 	}
 
 	render() {

+ 28 - 0
frontend/app/js/views/Station/Time.jsx

@@ -0,0 +1,28 @@
+import React, { Component } from "react";
+import PropTypes from "prop-types";
+
+import { connect } from "react-redux";
+
+const formatTime = (duration) => {
+	let d = moment.duration(duration, "seconds");
+	if (duration < 0) return "0:00";
+	return ((d.hours() > 0) ? (d.hours() < 10 ? ("0" + d.hours() + ":") : (d.hours() + ":")) : "") + (d.minutes() + ":") + (d.seconds() < 10 ? ("0" + d.seconds()) : d.seconds());
+};
+
+@connect(state => ({
+	duration: state.station.currentSong.getIn(["timings", "duration"]),
+	timeElapsed: state.station.currentSong.getIn(["timings", "timeElapsed"]),
+}))
+export default class Time extends Component {
+	constructor(props) {
+		super(props);
+	}
+
+	render() {
+		return (
+			<span className="time">
+				{ formatTime(this.props.timeElapsed) } / { formatTime(this.props.duration) }
+			</span>
+		);
+	}
+}

+ 1 - 1
frontend/app/js/views/Station/Views/QueueList.jsx

@@ -13,10 +13,10 @@ import io from "io";
 
 @connect(state => ({
 	user: {
+		loggedIn: state.user.get("loggedIn"),
 		userId: state.user.get("userId"),
 		role: state.user.get("role"),
 	},
-	loggedIn: state.user.get("loggedIn"),
 	stationId: state.station.get("id"),
 	stationOwner: state.station.get("ownerId"),
 	songTitle: state.songPlayer.get("title"),

+ 41 - 13
frontend/app/js/views/Station/Views/Settings.jsx

@@ -11,13 +11,14 @@ import { closeOverlay1 } from "actions/stationOverlay";
 import io from "io";
 
 @connect(state => ({
-	stationId: state.station.get("id"),
-	name: state.station.get("name"),
-	displayName: state.station.get("displayName"),
-	description: state.station.get("description"),
-	privacy: state.station.get("privacy"),
-	partyEnabled: state.station.get("partyMode"),
-	queueLocked: state.station.get("locked"),
+	stationId: state.station.info.get("stationId"),
+	name: state.station.info.get("name"),
+	displayName: state.station.info.get("displayName"),
+	description: state.station.info.get("description"),
+	privacy: state.station.info.get("privacy"),
+	mode: state.station.info.get("mode"),
+	//partyEnabled: state.station.get("partyMode"),
+	//queueLocked: state.station.get("locked"),
 }))
 export default class Settings extends Component {
 	constructor(props) {
@@ -26,8 +27,8 @@ export default class Settings extends Component {
 		CustomInput.initialize(this);
 
 		this.state = {
-			privacy: props.privacy,
-			mode: this.getModeTemp(props.partyEnabled, props.queueLocked),
+			//privacy: props.privacy,
+			//mode: this.getModeTemp(props.partyEnabled, props.queueLocked),
 			deleteButtonText: "Delete this station", //TODO Improve this
 		};
 	}
@@ -105,7 +106,7 @@ export default class Settings extends Component {
 		}
 	};
 
-	getModeTemp = (partyEnabled, queueLocked) => {
+	/*getModeTemp = (partyEnabled, queueLocked) => {
 		// If party enabled
 			// If queue locked
 				// Mode is DJ
@@ -118,11 +119,38 @@ export default class Settings extends Component {
 			if (queueLocked) return "dj";
 			else return "party";
 		} else return "normal";
+	}*/
+
+	translateModeTemp = (mode) => {
+		// If party enabled
+			// If queue locked
+				// Mode is DJ
+			// If queue not locked
+				// Mode party
+		// If party not enabled
+			// Mode is normal
+
+		if (mode === "normal") {
+			return {
+				partyMode: false,
+				queueLocked: true,
+			};
+		} else if (mode === "dj") {
+			return {
+				partyMode: true,
+				queueLocked: true,
+			};
+		} else if (mode === "party") {
+			return {
+				partyMode: true,
+				queueLocked: false,
+			};
+		}
 	}
 
 	// This is temporary since the backend cannot be changed in this update.
 	changeModeHandlerTemp = (newMode, cb) => {
-		const previousMode = this.state.mode;
+		const previousMode = this.props.mode;
 
 		const disablePartyMode = (cb) => {
 			io.getSocket((socket) => {
@@ -290,10 +318,10 @@ export default class Settings extends Component {
 					<CustomInput key="description" showLabel={true} type="stationDescription" name="description" label="Station description" placeholder="Station description" original={ this.props.description } onRef={ ref => (this.input.description = ref) } />
 					<button onClick={ this.changeDescription }>Change description</button>
 
-					<CustomInput key="privacy" showLabel={true} type="stationPrivacy" name="privacy" label="Station privacy" placeholder="Station privacy" original={ this.state.privacy } onRef={ ref => (this.input.privacy = ref) } />
+					<CustomInput key="privacy" showLabel={true} type="stationPrivacy" name="privacy" label="Station privacy" placeholder="Station privacy" original={ this.props.privacy } onRef={ ref => (this.input.privacy = ref) } />
 					<button onClick={ this.savePrivacy }>Save privacy</button>
 
-					<CustomInput key="mode" showLabel={true} type="stationMode" name="mode" label="Station mode" placeholder="Station mode" original={ this.state.mode } onRef={ ref => (this.input.mode = ref) } />
+					<CustomInput key="mode" showLabel={true} type="stationMode" name="mode" label="Station mode" placeholder="Station mode" original={ this.props.mode } onRef={ ref => (this.input.mode = ref) } />
 					<button onClick={ this.saveMode }>Save mode</button>
 
 					<button onClick={ this.deleteStation } className="red-button">{ this.state.deleteButtonText }</button>

+ 155 - 175
frontend/app/js/views/Station/index.jsx

@@ -7,57 +7,62 @@ import { translate, Trans } from "react-i18next";
 import Player from "./Player";
 import Seekerbar from "./Seekerbar";
 import VolumeSlider from "./VolumeSlider";
+import Ratings from "./Ratings";
+import Time from "./Time";
 import Overlays from "./Views/Overlays";
 
-import { changeVolume } from "actions/volume";
-import { changeSong, setTimeElapsed, timePaused, receivedRatings, receivedOwnRatings } from "actions/songPlayer";
-import { pauseStation, resumeStation } from "actions/station";
+import { actionCreators as stationCurrentSongActionCreators } from "ducks/stationCurrentSong";
+import { bindActionCreators } from "redux";
+
+//import { changeVolume } from "actions/volume";
+//import { changeSong, setTimeElapsed, timePaused, receivedRatings, receivedOwnRatings } from "actions/songPlayer";
+//import { pauseStation, resumeStation } from "actions/station";
 import { openOverlay1 } from "actions/stationOverlay";
-import { addSong } from "actions/playlistQueue";
+//import { addSong } from "actions/playlistQueue";
+//import { updateTimePaused } from "../../actions/songPlayer";
 
 import { connect } from "react-redux";
 
 import io from "io";
 import config from "config";
-import {updateTimePaused} from "../../actions/songPlayer";
-
-const formatTime = (duration) => {
-	let d = moment.duration(duration, "seconds");
-	if (duration < 0) return "0:00";
-	return ((d.hours() > 0) ? (d.hours() < 10 ? ("0" + d.hours() + ":") : (d.hours() + ":")) : "") + (d.minutes() + ":") + (d.seconds() < 10 ? ("0" + d.seconds()) : d.seconds());
-};
 
 @connect(state => ({
 	user: {
-		userId: state.user.get("userId"),
-		role: state.user.get("role"),
+		userId: state.session.get("userId"),
+		role: state.session.get("role"),
+		loggedIn: state.session.get("loggedIn"),
 	},
-	loggedIn: state.user.get("loggedIn"),
-	songId: state.songPlayer.get("songId"),
-	songTitle: state.songPlayer.get("title"),
-	songDuration: state.songPlayer.get("duration"),
-	songTimeElapsed: state.songPlayer.get("timeElapsed"),
-	songArtists: state.songPlayer.get("artists"),
-	songLikes: state.songPlayer.get("likes"),
-	songLiked: state.songPlayer.get("liked"),
-	songDislikes: state.songPlayer.get("dislikes"),
-	songDisliked: state.songPlayer.get("disliked"),
-	simpleSong: state.songPlayer.get("simple"),
-	songExists: state.songPlayer.get("exists"),
-	queueLocked: state.station.get("locked"),
-	partyEnabled: state.station.get("partyMode"),
-	station: {
-		stationId: state.station.get("id"),
-		name: state.station.get("name"),
-		displayName: state.station.get("displayName"),
-		paused: state.station.get("paused"),
-		pausedAt: state.station.get("pausedAt"),
-		ownerId: state.station.get("ownerId"),
+	/*
+	//queueLocked: state.station.get("locked"),
+	//partyEnabled: state.station.get("partyMode"),*/
+	song: {
+		exists: state.station.currentSong.get("songId") !== "",
+		songId: state.station.currentSong.get("songId"),
+		title: state.station.currentSong.get("title"),
+		artists: state.station.currentSong.get("artists"),
 	},
+	station: {
+		stationId: state.station.info.get("stationId"),
+		name: state.station.info.get("name"),
+		displayName: state.station.info.get("displayName"),
+		paused: state.station.info.get("paused"),
+		pausedAt: state.station.info.get("pausedAt"),
+		ownerId: state.station.info.get("ownerId"),
+	},/*
 	selectedPlaylistObject: {
 		addedSongId: state.playlistQueue.get("addedSongId"),
 		selectedPlaylistId: state.playlistQueue.get("playlistSelected"),
-	},
+	},*/
+}),
+(dispatch) => ({
+	onNextSong: bindActionCreators(stationCurrentSongActionCreators.nextSong, dispatch),
+	onLikeUpdate: bindActionCreators(stationCurrentSongActionCreators.likeUpdate, dispatch),
+	onDislikeUpdate: bindActionCreators(stationCurrentSongActionCreators.dislikeUpdate, dispatch),
+	onLikedUpdate: bindActionCreators(stationCurrentSongActionCreators.likedUpdate, dispatch),
+	onDislikedUpdate: bindActionCreators(stationCurrentSongActionCreators.dislikedUpdate, dispatch),
+	onPauseTime: bindActionCreators(stationCurrentSongActionCreators.pauseTime, dispatch),
+	onResumeTime: bindActionCreators(stationCurrentSongActionCreators.resumeTime, dispatch),
+	openOverlay1: bindActionCreators(openOverlay1, dispatch),
 }))
 
 @translate(["station"], { wait: true })
@@ -73,91 +78,117 @@ export default class Station extends Component {
 	constructor(props) {
 		super();
 
-		this.state = {
+		/*this.state = {
 			mode: this.getModeTemp(props.partyEnabled, props.queueLocked),
-		};
+		};*/
 
 		io.getSocket(socket => {
 			socket.emit("stations.join", props.station.name, res => {
-				if (res.status === 'success') {
+				if (res.status === "success") {
 					if (res.data.currentSong) {
-						res.data.currentSong.startedAt = res.data.startedAt;
-						res.data.currentSong.timePaused = res.data.timePaused;
+						let song = {
+							songId: res.data.currentSong.songId,
+							timings: {
+								duration: res.data.currentSong.duration,
+								skipDuration: res.data.currentSong.skipDuration,
+								// timeElapsed?
+								timePaused: res.data.timePaused,
+								// pausedAt?
+								startedAt: res.data.startedAt,
+							},
+							title: res.data.currentSong.title,
+							artists: res.data.currentSong.artists,
+							ratings: {
+								enabled: !(res.data.currentSong.likes === -1 && res.data.currentSong.dislikes === -1),
+								likes: res.data.currentSong.likes,
+								dislikes: res.data.currentSong.dislikes,
+							},
+						};
+						this.props.onNextSong(song);
+						this.fetchOwnRatings();
+					} else {
+						// TODO This will probably need to be handled
+						this.props.onNextSong(null);
 					}
-
-					this.props.dispatch(changeSong(res.data.currentSong));
-					this.getOwnRatings();
 				}
 
-				socket.on('event:songs.next', data => {
-					this.addTopToQueue();
+				socket.on("event:songs.next", data => {
+					//this.addTopToQueue();
 					if (data.currentSong) {
-						data.currentSong.startedAt = data.startedAt;
-						data.currentSong.timePaused = data.timePaused;
+						let song = {
+							songId: data.currentSong.songId,
+							timings: {
+								duration: data.currentSong.duration,
+								skipDuration: data.currentSong.skipDuration,
+								// timeElapsed?
+								timePaused: data.timePaused,
+								// pausedAt?
+								startedAt: data.startedAt,
+							},
+							title: data.currentSong.title,
+							artists: data.currentSong.artists,
+							ratings: {
+								enabled: !(data.currentSong.likes === -1 && data.currentSong.dislikes === -1),
+								likes: data.currentSong.likes,
+								dislikes: data.currentSong.dislikes,
+							},
+						};
+						this.props.onNextSong(song);
+						this.fetchOwnRatings();
+					} else {
+						this.props.onNextSong(null);
 					}
-					this.props.dispatch(changeSong(data.currentSong));
-					this.getOwnRatings();
 				});
 
-				socket.on('event:stations.pause', pausedAt => {
-					this.props.dispatch(pauseStation(pausedAt));
+				socket.on("event:stations.pause", pausedAt => {
+					// TODO Dispatch to station info
+					this.props.onPauseTime(pausedAt);
 				});
-
-				socket.on('event:stations.resume', data => {
-					this.props.dispatch(updateTimePaused(data.timePaused));
-					this.props.dispatch(resumeStation());
+				socket.on("event:stations.resume", data => {
+					// TODO Dispatch to station info
+					this.props.onResumeTime(data.timePaused);
 				});
-
-				socket.on('event:song.like', data => {
-					console.log("LIKE");
-					if (this.props.songExists) {
-						if (data.songId === this.props.songId) {
-							this.props.dispatch(receivedRatings(data.likes, data.dislikes));
-						}
+				socket.on("event:song.like", data => {
+					if (data.songId === this.props.song.songId) {
+						this.props.onLikeUpdate(data.likes);
+						this.props.onDislikeUpdate(data.dislikes);
 					}
 				});
-				socket.on('event:song.dislike', data => {
-					console.log("DISLIKE");
-					if (this.props.songExists) {
-						if (data.songId === this.props.songId) {
-							this.props.dispatch(receivedRatings(data.likes, data.dislikes));
-						}
+				socket.on("event:song.dislike", data => {
+					if (data.songId === this.props.song.songId) {
+						this.props.onLikeUpdate(data.likes);
+						this.props.onDislikeUpdate(data.dislikes);
 					}
 				});
-				socket.on('event:song.unlike', data => {
-					console.log("UNLIKE");
-					if (this.props.songExists) {
-						if (data.songId === this.props.songId) {
-							this.props.dispatch(receivedRatings(data.likes, data.dislikes));
-						}
+				socket.on("event:song.unlike", data => {
+					if (data.songId === this.props.song.songId) {
+						this.props.onLikeUpdate(data.likes);
+						this.props.onDislikeUpdate(data.dislikes);
 					}
 				});
-				socket.on('event:song.undislike', data => {
-					console.log("UNDISLIKE");
-					if (this.props.songExists) {
-						if (data.songId === this.props.songId) {
-							this.props.dispatch(receivedRatings(data.likes, data.dislikes));
-						}
+				socket.on("event:song.undislike", data => {
+					if (data.songId === this.props.song.songId) {
+						this.props.onLikeUpdate(data.likes);
+						this.props.onDislikeUpdate(data.dislikes);
 					}
 				});
-				socket.on('event:song.newRatings', data => {
-					if (this.props.songExists) {
-						if (data.songId === this.props.songId) {
-							this.props.dispatch(receivedOwnRatings(data.liked, data.disliked));
-						}
+				socket.on("event:song.newRatings", data => {
+					if (data.songId === this.props.song.songId) {
+						this.props.onLikedUpdate(data.liked);
+						this.props.onDislikedUpdate(data.disliked);
 					}
 				});
 			});
 		});
 
-		setInterval(() => {
-			if (this.props.songExists) {
+		/*setInterval(() => {
+			if (this.props.song.exists) {
 				this.props.dispatch(setTimeElapsed(this.props.station.paused, this.props.station.pausedAt)); // TODO Fix
 			}
-		}, 1000);
+		}, 1000);*/
 	}
 
-	isInQueue = (songId, cb) => {
+	/*isInQueue = (songId, cb) => {
 		io.getSocket((socket) => {
 			socket.emit('stations.getQueue', this.props.stationId, data => {
 				if (data.status === 'success') {
@@ -171,15 +202,15 @@ export default class Station extends Component {
 				return cb(false);
 			});
 		});
-	};
+	};*/
 
-	checkIfCanAdd = (cb) => {
+	/*checkIfCanAdd = (cb) => {
 		if (this.state.mode === "normal") return cb(false);
 		let playlistId = this.props.selectedPlaylistObject.selectedPlaylistId;
 		let songId = this.props.selectedPlaylistObject.addedSongId;
-		console.log(playlistId, songId, this.props.songId);
+		console.log(playlistId, songId, this.props.song.songId);
 		if (playlistId) {
-			if (songId === this.props.songId) return cb(true);
+			if (songId === this.props.song.songId) return cb(true);
 			else if (songId === null) return cb(true);
 			else {
 				this.isInQueue(songId, (res) => {
@@ -187,9 +218,9 @@ export default class Station extends Component {
 				});
 			}
 		}
-	}
+	}*/
 
-	addTopToQueue = () => {
+	/*addTopToQueue = () => {
 		console.log("ADD TOP TO QUEUE!!!");
 		this.checkIfCanAdd((can) => {
 			if (!can) return;
@@ -226,17 +257,17 @@ export default class Station extends Component {
 				});
 			});
 		});
-	};
+	};*/
 
-	moveToBottom = (playlistId, songId, cb) => {
+	/*moveToBottom = (playlistId, songId, cb) => {
 		io.getSocket((socket) => {
 			socket.emit('playlists.moveSongToBottom', playlistId, songId, data => {
 				cb(data);
 			});
 		});
-	}
+	}*/
 
-	getModeTemp = (partyEnabled, queueLocked) => {
+	/*getModeTemp = (partyEnabled, queueLocked) => {
 		// If party enabled
 		// If queue locked
 		// Mode is DJ
@@ -249,19 +280,22 @@ export default class Station extends Component {
 			if (queueLocked) return "dj";
 			else return "party";
 		} else return "normal";
-	}
+	}*/
 
-	getOwnRatings = () => {
+	fetchOwnRatings = () => {
 		io.getSocket((socket) => {
-			if (!this.props.songExists) return;
-			socket.emit('songs.getOwnSongRatings', this.props.songId, (data) => {
-				if (this.props.songId === data.songId) this.props.dispatch(receivedOwnRatings(data.liked, data.disliked));
+			if (!this.props.song.exists) return;
+			socket.emit("songs.getOwnSongRatings", this.props.song.songId, (data) => {
+				if (this.props.song.songId === data.songId) {
+					this.props.onLikedUpdate(data.liked);
+					this.props.onDislikedUpdate(data.disliked);
+				}
 			});
 		});
 	};
 
 	isOwner = () => {
-		if (this.props.loggedIn) {
+		if (this.props.user.loggedIn) {
 			if (this.props.user.role === "admin") return true;
 			if (this.props.user.userId === this.props.station.ownerId) return true;
 		}
@@ -279,73 +313,23 @@ export default class Station extends Component {
 
 	resumeStation = () => {
 		io.getSocket(socket => {
-			socket.emit('stations.resume', this.props.station.stationId, data => {
-
+			socket.emit("stations.resume", this.props.station.stationId, data => {
+				// TODO Handle error/success
 			});
 		});
 	};
 
 	pauseStation = () => {
 		io.getSocket(socket => {
-			socket.emit('stations.pause', this.props.station.stationId, data => {
-
+			socket.emit("stations.pause", this.props.station.stationId, data => {
+				// TODO Handle error/success
 			});
 		});
 	};
 
-	getRatings = () => {
-		const likes = <span>{ this.props.songLikes }</span>;
-		const dislikes = <span>{ this.props.songDislikes }</span>;
-		let likeButton = <i className="material-icons disabled">thumb_up</i>;
-		let dislikeButton = <i className="material-icons disabled">thumb_down</i>;
-
-		if (this.props.loggedIn) {
-			if (this.props.songLiked) likeButton = <i className="material-icons liked" onClick={ this.unlike }>thumb_up</i>;
-			else likeButton = <i className="material-icons" onClick={ this.like }>thumb_up</i>;
-
-			if (this.props.songDisliked) dislikeButton = <i className="material-icons disliked" onClick={ this.undislike }>thumb_down</i>;
-			else dislikeButton = <i className="material-icons" onClick={ this.dislike }>thumb_down</i>;
-		}
-
-		return <div className="ratings-container">
-			<div>
-				{ likeButton }
-				{ likes }
-			</div>
-			<div>
-				{ dislikeButton }
-				{ dislikes }
-			</div>
-		</div>;
-	};
-
-	like = () => {
-		io.getSocket(socket => {
-			socket.emit('songs.like', this.props.songId, data => {});
-		});
-	};
-
-	dislike = () => {
-		io.getSocket(socket => {
-			socket.emit('songs.dislike', this.props.songId, data => {});
-		});
-	};
-
-	unlike = () => {
-		io.getSocket(socket => {
-			socket.emit('songs.unlike', this.props.songId, data => {});
-		});
-	};
-
-	undislike = () => {
-		io.getSocket(socket => {
-			socket.emit('songs.undislike', this.props.songId, data => {});
-		});
-	};
-
 	skipStation = () => {
 		io.getSocket(socket => {
-			socket.emit('stations.forceSkip', this.props.station.stationId, data => {});
+			socket.emit("stations.forceSkip", this.props.station.stationId, data => {});
 		});
 	}
 
@@ -359,9 +343,9 @@ export default class Station extends Component {
 				<Overlays t={ this.props.t } />
 
 				<div id="sidebar">
-					<button onClick={ () => { this.props.dispatch(openOverlay1("users")) } }><i className="material-icons">people</i></button>
-					<button onClick={ () => { this.props.dispatch(openOverlay1("queueList")) } }><i className="material-icons">queue_music</i></button>
-					<button onClick={ () => { this.props.dispatch(openOverlay1("playlists")) } }><i className="material-icons">library_music</i></button>
+					<button onClick={ () => { this.props.openOverlay1("users") } }><i className="material-icons">people</i></button>
+					<button onClick={ () => { this.props.openOverlay1("queueList") } }><i className="material-icons">queue_music</i></button>
+					<button onClick={ () => { this.props.openOverlay1("playlists") } }><i className="material-icons">library_music</i></button>
 					<hr/>
 					{
 						(this.isOwner())
@@ -377,14 +361,14 @@ export default class Station extends Component {
 					}
 					{
 						(this.isOwner())
-						? <button onClick={ () => { this.props.dispatch(openOverlay1("settings")) } }><i className="material-icons">settings</i></button>
+						? <button onClick={ () => { this.props.openOverlay1("settings") } }><i className="material-icons">settings</i></button>
 						: null
 					}
 				</div>
 
-				<h1>{ this.props.station.displayName }</h1>
+				<h1 onClick={ this.addSongTemp }>{ this.props.station.displayName }</h1>
 
-				<div className={(!this.props.songExists) ? "player-container hidden" : "player-container"}>
+				<div className={(!this.props.song.exists) ? "player-container hidden" : "player-container"}>
 					<div className="iframe-container">
 						<Player onRef={ ref => (this.player = ref) }/>
 						{ (this.props.station.paused) ? <div className="paused-overlay"><span>Paused</span><i className="material-icons">pause</i></div> : null }
@@ -392,18 +376,14 @@ export default class Station extends Component {
 					<Seekerbar/>
 				</div>
 
-				{ (this.props.songExists) ? (
+				{ (this.props.song.exists) ? (
 				[
 					<div key="content" className="content">
-						<span className="title">{ this.props.songTitle }</span>
-						<span className="artists">{ this.props.songArtists.join(", ") }</span>
-						<span className="time">
-							{ formatTime(this.props.songTimeElapsed) } / { formatTime(this.props.songDuration) }
-						</span>
+						<span className="title">{ this.props.song.title }</span>
+						<span className="artists">{ this.props.song.artists.join(", ") }</span>
+						<Time/>
 						<VolumeSlider/>
-						{
-							(!this.props.simpleSong) ? this.getRatings() : null
-						}
+						<Ratings/>
 					</div>,
 				]) : (
 					<h1>No song playing</h1>