瀏覽代碼

Fixed issue with timings, worked on ratings, added overlay system.

KrisVos130 7 年之前
父節點
當前提交
b1bd137e56

+ 16 - 0
frontend/app/js/actions/songPlayer.js

@@ -1,6 +1,8 @@
 export const CHANGE_SONG = "CHANGE_SONG";
 export const SET_TIME_ELAPSED = "SET_TIME_ELAPSED";
 export const UPDATE_TIME_PAUSED = "UPDATE_TIME_PAUSED";
+export const RECEIVED_RATINGS = "RECEIVED_RATINGS";
+export const RECEIVED_OWN_RATINGS = "RECEIVED_OWN_RATINGS";
 
 export function changeSong(song) {
 	return {
@@ -20,4 +22,18 @@ export function updateTimePaused(timePaused) {
 		type: UPDATE_TIME_PAUSED,
 		timePaused,
 	};
+}
+export function receivedRatings(likes, dislikes) {
+	return {
+		type: RECEIVED_RATINGS,
+		likes, //TODO Check songId
+		dislikes,
+	};
+}
+export function receivedOwnRatings(liked, disliked) {
+	return {
+		type: RECEIVED_OWN_RATINGS,
+		liked, //TODO Check songId
+		disliked,
+	};
 }

+ 27 - 0
frontend/app/js/actions/stationOverlay.js

@@ -0,0 +1,27 @@
+export const OPEN_OVERLAY1 = "OPEN_OVERLAY1";
+export const OPEN_OVERLAY2 = "OPEN_OVERLAY2";
+export const CLOSE_OVERLAY1 = "CLOSE_OVERLAY1";
+export const CLOSE_OVERLAY2 = "CLOSE_OVERLAY2";
+
+export function openOverlay1(overlay) {
+	return {
+		type: OPEN_OVERLAY1,
+		overlay,
+	};
+}
+export function openOverlay2(overlay) {
+	return {
+		type: OPEN_OVERLAY2,
+		overlay,
+	};
+}
+export function closeOverlay1() {
+	return {
+		type: CLOSE_OVERLAY1,
+	};
+}
+export function closeOverlay2() {
+	return {
+		type: CLOSE_OVERLAY2,
+	};
+}

+ 9 - 0
frontend/app/js/app.jsx

@@ -165,6 +165,15 @@ class App extends Component { // eslint-disable-line react/no-multi-comp
 						auth="station"
 						title="TODO"
 					/>
+					<AuthRoute
+						path="/official/:name"
+						component={ asyncComponent({
+							resolve: () => System.import("views/Station"),
+							name: "Station",
+						})}
+						auth="station"
+						title="TODO"
+					/>
 					<AuthRoute
 						exact
 						path="/"

+ 2 - 0
frontend/app/js/reducers/index.js

@@ -3,10 +3,12 @@ import user from "reducers/user";
 import volume from "reducers/volume";
 import songPlayer from "reducers/songPlayer";
 import station from "reducers/station";
+import stationOverlay from "reducers/stationOverlay";
 
 export default combineReducers({
 	user,
 	volume,
 	songPlayer,
 	station,
+	stationOverlay,
 });

+ 18 - 0
frontend/app/js/reducers/songPlayer.js

@@ -4,6 +4,8 @@ import {
 	CHANGE_SONG,
 	SET_TIME_ELAPSED,
 	UPDATE_TIME_PAUSED,
+	RECEIVED_RATINGS,
+	RECEIVED_OWN_RATINGS,
 } from "actions/songPlayer";
 
 const initialState = Map({
@@ -15,7 +17,9 @@ const initialState = Map({
 	skipDuration: 0,
 	songId: "",
 	dislikes: 0,
+	disliked: false,
 	likes: 0,
+	liked: false,
 	startedAt: 0,
 	timePaused: 0,
 	timeElapsed: 0,
@@ -34,7 +38,9 @@ const actionsMap = {
 			obj.skipDuration = action.song.skipDuration;
 			obj.songId = action.song.songId;
 			obj.dislikes = action.song.dislikes;
+			obj.disliked = false;
 			obj.likes = action.song.likes;
+			obj.liked = false;
 			obj.startedAt = action.song.startedAt;
 			obj.timePaused = action.song.timePaused;
 			obj.timeElapsed = 0;
@@ -56,6 +62,18 @@ const actionsMap = {
 			timePaused: action.timePaused,
 		});
 	},
+	[RECEIVED_RATINGS]: (state, action) => {
+		return state.merge({
+			likes: action.likes,
+			dislikes: action.dislikes,
+		});
+	},
+	[RECEIVED_OWN_RATINGS]: (state, action) => {
+		return state.merge({
+			liked: action.liked,
+			disliked: action.disliked,
+		});
+	},
 };
 
 export default function reducer(state = initialState, action = {}) {

+ 41 - 0
frontend/app/js/reducers/stationOverlay.js

@@ -0,0 +1,41 @@
+import { Map } from "immutable";
+
+import {
+	OPEN_OVERLAY1,
+	OPEN_OVERLAY2,
+	CLOSE_OVERLAY1,
+	CLOSE_OVERLAY2,
+} from "actions/stationOverlay";
+
+const initialState = Map({
+	overlay1: null,
+	overlay2: null,
+});
+
+const actionsMap = {
+	[OPEN_OVERLAY1]: (state, action) => {
+		return state.merge({
+			overlay1: action.overlay,
+		});
+	},
+	[OPEN_OVERLAY2]: (state, action) => {
+		return state.merge({
+			overlay2: action.overlay,
+		});
+	},
+	[CLOSE_OVERLAY1]: (state, action) => {
+		return state.merge({
+			overlay1: null,
+		});
+	},
+	[CLOSE_OVERLAY2]: (state, action) => {
+		return state.merge({
+			overlay2: null,
+		});
+	},
+};
+
+export default function reducer(state = initialState, action = {}) {
+	const fn = actionsMap[action.type];
+	return fn ? fn(state, action) : state;
+}

+ 43 - 0
frontend/app/js/views/Station/Views/Overlays.jsx

@@ -0,0 +1,43 @@
+import React, { Component } from "react";
+import PropTypes from "prop-types";
+
+import { connect } from "react-redux";
+
+import Settings from "./Settings";
+
+@connect(state => ({
+	overlay1: state.stationOverlay.get("overlay1"),
+	overlay2: state.stationOverlay.get("overlay2"),
+}))
+export default class Overlays extends Component {
+	constructor(props) {
+		super(props);
+
+		this.state = {
+			overlay1: null,
+			overlay2: null,
+		};
+	}
+
+	getComponent = (type, key) => {
+		if (type === "settings") {
+			return <Settings key={ key }/>;
+		} else return null;
+	};
+
+	componentDidUpdate(prevProps, prevState) {
+		console.log(111, prevProps.overlay1, this.props.overlay1);
+		if (this.props.overlay1 !== prevProps.overlay1) {
+			this.setState({
+				overlay1: this.getComponent(this.props.overlay1),
+			});
+		}
+	}
+
+	render() {
+		return <div>
+			{ this.state.overlay1 }
+			{ this.state.overlay2 }
+		</div>;
+	}
+}

+ 36 - 0
frontend/app/js/views/Station/Views/Settings.jsx

@@ -0,0 +1,36 @@
+import React, { Component } from "react";
+import PropTypes from "prop-types";
+
+import { connect } from "react-redux";
+
+import { closeOverlay1 } from "actions/stationOverlay";
+
+@connect(state => ({
+	name: state.station.get("name"),
+	displayName: state.station.get("displayName"),
+	description: state.station.get("description"),
+}))
+export default class Settings extends Component {
+	constructor(props) {
+		super(props);
+
+		this.state = {
+			name: props.name,
+			displayName: props.displayName,
+			description: props.description,
+		};
+	}
+
+	close = () => {
+		this.props.dispatch(closeOverlay1());
+	};
+
+	render() {
+		return (
+			<div className="overlay">
+				<button onClick={ this.close }>Back</button>
+				<h1>Settings</h1>
+			</div>
+		);
+	}
+}

+ 119 - 21
frontend/app/js/views/Station/index.jsx

@@ -7,10 +7,12 @@ import { translate, Trans } from "react-i18next";
 import Player from "./Player";
 import Seekerbar from "./Seekerbar";
 import VolumeSlider from "./VolumeSlider";
+import Overlays from "./Views/Overlays";
 
 import { changeVolume } from "actions/volume";
-import { changeSong, setTimeElapsed, timePaused } from "actions/songPlayer";
+import { changeSong, setTimeElapsed, timePaused, receivedRatings, receivedOwnRatings } from "actions/songPlayer";
 import { pauseStation, resumeStation } from "actions/station";
+import { openOverlay1 } from "actions/stationOverlay";
 
 import { connect } from "react-redux";
 
@@ -18,10 +20,6 @@ import io from "io";
 import config from "config";
 import {updateTimePaused} from "../../actions/songPlayer";
 
-const Aux = (props) => {
-	return props.children;
-};
-
 const formatTime = (duration) => {
 	let d = moment.duration(duration, "seconds");
 	if (duration < 0) return "0:00";
@@ -34,10 +32,15 @@ const formatTime = (duration) => {
 		role: state.user.get("role"),
 	},
 	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"),
 	station: {
@@ -64,21 +67,23 @@ export default class Station extends Component {
 
 		io.getSocket(socket => {
 			socket.emit("stations.join", props.station.name, res => {
-				console.log(res);
 				if (res.status === 'success') {
 					if (res.data.currentSong) {
 						res.data.currentSong.startedAt = res.data.startedAt;
 						res.data.currentSong.timePaused = res.data.timePaused;
 					}
+
 					this.props.dispatch(changeSong(res.data.currentSong));
+					this.getOwnRatings();
 				}
 
 				socket.on('event:songs.next', data => {
 					if (data.currentSong) {
-						data.currentSong.startedAt = res.data.startedAt;
-						data.currentSong.timePaused = res.data.timePaused;
+						data.currentSong.startedAt = data.startedAt;
+						data.currentSong.timePaused = data.timePaused;
 					}
 					this.props.dispatch(changeSong(data.currentSong));
+					this.getOwnRatings();
 				});
 
 				socket.on('event:stations.pause', pausedAt => {
@@ -89,6 +94,47 @@ export default class Station extends Component {
 					this.props.dispatch(updateTimePaused(data.timePaused));
 					this.props.dispatch(resumeStation());
 				});
+
+
+				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.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.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.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.newRatings', data => {
+					if (this.props.songExists) {
+						if (data.songId === this.props.songId) {
+							this.props.dispatch(receivedOwnRatings(data.liked, data.disliked));
+						}
+					}
+				});
 			});
 		});
 
@@ -101,17 +147,9 @@ export default class Station extends Component {
 
 	getOwnRatings = () => {
 		io.getSocket((socket) => {
-			if (!this.state.currentSongExists) return;
-			socket.emit('songs.getOwnSongRatings', this.state.currentSong.songId, (data) => {
-				if (this.state.currentSong.songId === data.songId) {
-					this.setState({
-						currentSong: {
-							...this.state.currentSong,
-							liked: data.liked,
-							disliked: data.disliked,
-						},
-					});
-				}
+			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));
 			});
 		});
 	};
@@ -149,6 +187,58 @@ export default class Station extends Component {
 		});
 	};
 
+	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>
+			{ likeButton }
+			{ likes }
+			{ dislikeButton }
+			{ dislikes }
+		</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 => {});
+		});
+	};
+
+	skip = () => {
+		io.getSocket(socket => {
+			socket.emit('stations.forceSkip', this.props.station.stationId, data => {});
+		});
+	}
+
 	render() {
 		const { t } = this.props;
 
@@ -156,15 +246,21 @@ export default class Station extends Component {
 
 		return (
 			<main id="station">
+				<Overlays />
+
+				<button onClick={ () => { this.props.dispatch(openOverlay1("settings")) } }>Open settings</button>
+
 				<h1>{ this.props.station.displayName }</h1>
 
 				{ (this.props.station.paused) ? <button onClick={ this.resumeStation }>Resume</button> : <button onClick={ this.pauseStation }>Pause</button>}
 
 				<button onClick={ this.addSongTemp }>Add song to queue TEMP</button>
+				<button onClick={ this.skip }>Skip</button>
 
 				<hr/>
 				<div className={(!this.props.songExists) ? "hidden" : ""}>
 					<Player onRef={ ref => (this.player = ref) }/>
+					{ (this.props.station.paused) ? <div><span>Paused</span><i className="material-icons">pause</i></div> : null }
 				</div>
 
 				{ (this.props.songExists) ? (
@@ -172,15 +268,17 @@ export default class Station extends Component {
 					<div key="content">
 						<h1>Title: { this.props.songTitle }</h1>
 						<br/>
-						Paused: { (this.props.station.paused) ? "true" : "false" }
-						<br/>
 						<span>Artists: { this.props.songArtists.join(", ") }</span>
+						<br/>
 						<span key="time">
 							{ formatTime(this.props.songTimeElapsed) } - { formatTime(this.props.songDuration) }
 						</span>
 						<div key="seekerbar" className="seekerbar-container" style={{"width": "100%", "background-color": "yellow", "height": "20px", "display": "block"}}>
 							<Seekerbar/>
 						</div>
+						{
+							(!this.props.simpleSong) ? this.getRatings() : null
+						}
 						<VolumeSlider key="volumeSlider"/>
 					</div>,
 				]) : (

+ 8 - 0
frontend/app/styles/main.scss

@@ -48,6 +48,14 @@ h1 {
 	display: none;
 }
 
+.liked {
+	color: green;
+}
+
+.disliked {
+	color: red;
+}
+
 main {
 	margin-left: auto;
 	margin-right: auto;