Browse Source

refactor: Made vote skip toggleable

Owen Diffey 1 year ago
parent
commit
a458cae878

+ 38 - 36
backend/logic/actions/stations.js

@@ -213,11 +213,12 @@ CacheModule.runJob("SUB", {
 });
 
 CacheModule.runJob("SUB", {
-	channel: "station.voteSkipSong",
-	cb: stationId => {
+	channel: "station.toggleSkipVote",
+	cb: res => {
+		const { stationId, voted, userId } = res;
 		WSModule.runJob("EMIT_TO_ROOM", {
 			room: `station.${stationId}`,
-			args: ["event:station.voteSkipSong"]
+			args: ["event:station.toggleSkipVote", { data: { voted, userId } }]
 		});
 	}
 });
@@ -1124,13 +1125,13 @@ export default {
 	},
 
 	/**
-	 * Votes to skip a station
+	 * Toggle votes to skip a station
 	 *
 	 * @param session
 	 * @param stationId - the station id
 	 * @param cb
 	 */
-	voteSkip: isLoginRequired(async function voteSkip(session, stationId, cb) {
+	toggleSkipVote: isLoginRequired(async function toggleSkipVote(session, stationId, cb) {
 		const stationModel = await DBModule.runJob("GET_MODEL", { modelName: "station" }, this);
 
 		async.waterfall(
@@ -1153,51 +1154,55 @@ export default {
 
 				(station, next) => {
 					if (!station.currentSong) return next("There is currently no song to skip.");
-					if (station.currentSong.skipVotes.indexOf(session.userId) !== -1)
-						return next("You have already voted to skip this song.");
-					return next(null, station);
-				},
-
-				(station, next) => {
-					stationModel.updateOne(
-						{ _id: stationId },
-						{ $push: { "currentSong.skipVotes": session.userId } },
-						next
-					);
+					const query = {};
+					const voted = station.currentSong.skipVotes.indexOf(session.userId) !== -1;
+					if (!voted) query.$push = { "currentSong.skipVotes": session.userId };
+					else query.$pull = { "currentSong.skipVotes": session.userId };
+					return stationModel.updateOne({ _id: stationId }, query, err => {
+						if (err) next(err);
+						else next(null, !voted);
+					});
 				},
 
-				(res, next) => {
+				(voted, next) => {
 					StationsModule.runJob("UPDATE_STATION", { stationId }, this)
 						.then(station => {
-							next(null, station);
+							next(null, station, voted);
 						})
 						.catch(next);
 				},
 
-				(station, next) => {
+				(station, voted, next) => {
 					if (!station) return next("Station not found.");
 
-					return StationsModule.runJob("PROCESS_VOTE_SKIPS", { stationId }, this)
-						.then(() => next())
+					return StationsModule.runJob("PROCESS_SKIP_VOTES", { stationId }, this)
+						.then(() => next(null, voted))
 						.catch(next);
 				}
 			],
-			async err => {
+			async (err, voted) => {
 				if (err) {
 					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
-					this.log("ERROR", "STATIONS_VOTE_SKIP", `Vote skipping station "${stationId}" failed. "${err}"`);
+					this.log(
+						"ERROR",
+						"STATIONS_TOGGLE_SKIP_VOTE",
+						`Toggling skip vote on "${stationId}" failed. "${err}"`
+					);
 					return cb({ status: "error", message: err });
 				}
-				this.log("SUCCESS", "STATIONS_VOTE_SKIP", `Vote skipping "${stationId}" successful.`);
+				this.log("SUCCESS", "STATIONS_TOGGLE_SKIP_VOTE", `Toggling skip vote on "${stationId}" successful.`);
 
 				CacheModule.runJob("PUB", {
-					channel: "station.voteSkipSong",
-					value: stationId
+					channel: "station.toggleSkipVote",
+					value: { stationId, voted, userId: session.userId }
 				});
 
 				return cb({
 					status: "success",
-					message: "Successfully voted to skip the song."
+					message: voted
+						? "Successfully voted to skip the song."
+						: "Successfully removed vote to skip the song.",
+					data: { voted }
 				});
 			}
 		);
@@ -1521,7 +1526,7 @@ export default {
 				},
 
 				next => {
-					StationsModule.runJob("PROCESS_VOTE_SKIPS", { stationId }, this)
+					StationsModule.runJob("PROCESS_SKIP_VOTES", { stationId }, this)
 						.then(() => next())
 						.catch(next);
 				}
@@ -2604,12 +2609,14 @@ export default {
 					if (currentSong && currentSong._id === songId)
 						next(null, {
 							skipVotes: currentSong.skipVotes.length,
-							skipVotesCurrent: true
+							skipVotesCurrent: true,
+							voted: currentSong.skipVotes.indexOf(session.userId) !== -1
 						});
 					else
 						next(null, {
 							skipVotes: 0,
-							skipVotesCurrent: false
+							skipVotesCurrent: false,
+							voted: false
 						});
 				}
 			],
@@ -2624,14 +2631,9 @@ export default {
 					return cb({ status: "error", message: err });
 				}
 
-				const { skipVotes, skipVotesCurrent } = data;
-
 				return cb({
 					status: "success",
-					data: {
-						skipVotes,
-						skipVotesCurrent
-					}
+					data
 				});
 			}
 		);

+ 2 - 2
backend/logic/stations.js

@@ -771,9 +771,9 @@ class _StationsModule extends CoreClass {
 	 * @param {string} payload.stationId - the id of the station to process
 	 * @returns {Promise} - returns a promise (resolve, reject)
 	 */
-	PROCESS_VOTE_SKIPS(payload) {
+	PROCESS_SKIP_VOTES(payload) {
 		return new Promise((resolve, reject) => {
-			StationsModule.log("INFO", `Processing vote skips for station ${payload.stationId}.`);
+			StationsModule.log("INFO", `Processing skip votes for station ${payload.stationId}.`);
 
 			async.waterfall(
 				[

+ 35 - 16
frontend/src/pages/Station/index.vue

@@ -392,12 +392,12 @@ const playVideo = () => {
 		}, 150);
 	}
 };
-const voteSkipStation = (message?) => {
-	socket.dispatch("stations.voteSkip", station.value._id, data => {
+const toggleSkipVote = (message?) => {
+	socket.dispatch("stations.toggleSkipVote", station.value._id, data => {
 		if (data.status !== "success") new Toast(`Error: ${data.message}`);
 		else
 			new Toast(
-				message || "Successfully voted to skip the current song."
+				message || "Successfully toggled vote to skip the current song."
 			);
 	});
 };
@@ -453,9 +453,12 @@ const youtubeReady = () => {
 					console.log("error with youtube video", err);
 
 					if (err.data === 150 && loggedIn.value) {
-						if (!(localPaused.value || stationPaused.value)) {
+						if (
+							!(localPaused.value || stationPaused.value) &&
+							!currentSong.value.voted
+						) {
 							// automatically vote to skip
-							voteSkipStation(
+							toggleSkipVote(
 								"Automatically voted to skip as this song isn't available for you."
 							);
 						}
@@ -624,14 +627,15 @@ const setCurrentSong = data => {
 			currentSongId,
 			res => {
 				if (res.status === "success") {
-					const { skipVotes, skipVotesCurrent } = res.data;
+					const { skipVotes, skipVotesCurrent, voted } = res.data;
 					if (
 						!noSong.value &&
 						currentSong.value._id === currentSongId
 					) {
 						updateCurrentSongSkipVotes({
 							skipVotes,
-							skipVotesCurrent
+							skipVotesCurrent,
+							voted
 						});
 					}
 				}
@@ -660,9 +664,10 @@ const setCurrentSong = data => {
 						if (
 							autoSkipDisliked.value &&
 							res.data.disliked === true &&
-							!(localPaused.value || stationPaused.value)
+							!(localPaused.value || stationPaused.value) &&
+							!currentSong.value.voted
 						) {
-							voteSkipStation(
+							toggleSkipVote(
 								"Automatically voted to skip disliked song."
 							);
 						}
@@ -1184,7 +1189,7 @@ onMounted(async () => {
 		},
 		nexttrack: () => {
 			if (hasPermission("stations.skip")) skipStation();
-			else voteSkipStation();
+			else if (!currentSong.value.voted) toggleSkipVote();
 		}
 	});
 
@@ -1287,11 +1292,17 @@ onMounted(async () => {
 		updateNextSong(nextSong);
 	});
 
-	socket.on("event:station.voteSkipSong", () => {
+	socket.on("event:station.toggleSkipVote", res => {
 		if (currentSong.value)
 			updateCurrentSongSkipVotes({
-				skipVotes: currentSong.value.skipVotes + 1,
-				skipVotesCurrent: null
+				skipVotes: res.data.voted
+					? currentSong.value.skipVotes + 1
+					: currentSong.value.skipVotes - 1,
+				skipVotesCurrent: null,
+				voted:
+					res.data.userId === userId.value
+						? res.data.voted
+						: currentSong.value.voted
 			});
 	});
 
@@ -1727,9 +1738,17 @@ onBeforeUnmount(() => {
 									</button>
 									<button
 										v-else-if="loggedIn"
-										class="button is-primary"
-										@click="voteSkipStation()"
-										content="Vote to Skip Song"
+										:class="[
+											'button',
+											'is-primary',
+											{ voted: currentSong.voted }
+										]"
+										@click="toggleSkipVote()"
+										:content="`${
+											currentSong.voted
+												? 'Remove vote'
+												: 'Vote'
+										} to Skip Song`"
 										v-tippy
 									>
 										<i

+ 2 - 1
frontend/src/stores/station.ts

@@ -117,10 +117,11 @@ export const useStationStore = defineStore("station", {
 			this.currentSong.liked = ownSongRatings.liked;
 			this.currentSong.disliked = ownSongRatings.disliked;
 		},
-		updateCurrentSongSkipVotes({ skipVotes, skipVotesCurrent }) {
+		updateCurrentSongSkipVotes({ skipVotes, skipVotesCurrent, voted }) {
 			this.currentSong.skipVotes = skipVotes;
 			if (skipVotesCurrent !== null)
 				this.currentSong.skipVotesCurrent = skipVotesCurrent;
+			this.currentSong.voted = voted;
 		},
 		addPlaylistToAutoRequest(playlist) {
 			this.autoRequest.push(playlist);

+ 1 - 0
frontend/src/types/song.ts

@@ -35,6 +35,7 @@ export interface Song {
 export interface CurrentSong extends Song {
 	skipVotes: number;
 	skipVotesCurrent: number;
+	voted: boolean;
 	likes: number;
 	dislikes: number;
 	liked: boolean;