瀏覽代碼

fix(DraggableQueue): more efficient code, should reduce rubber banding

Signed-off-by: Jonathan <theflametrooper@gmail.com>
Jonathan 3 年之前
父節點
當前提交
ff707aacce

+ 50 - 23
backend/logic/actions/stations.js

@@ -324,6 +324,16 @@ CacheModule.runJob("SUB", {
 	}
 });
 
+CacheModule.runJob("SUB", {
+	channel: "station.repositionSongInQueue",
+	cb: res => {
+		WSModule.runJob("EMIT_TO_ROOM", {
+			room: `station.${res.stationId}`,
+			args: ["event:queue.repositionSong", res.song]
+		});
+	}
+});
+
 CacheModule.runJob("SUB", {
 	channel: "station.voteSkipSong",
 	cb: stationId => {
@@ -3075,64 +3085,81 @@ export default {
 	},
 
 	/**
-	 * Reposition station queue
+	 * Reposition a song in station queue
 	 *
-	 * @param session
-	 * @param stationId - the station id
-	 * @param queue - queue data
-	 * @param cb
+	 * @param {object} session - user session
+	 * @param {object} song - contains details about the song that is to be repositioned
+	 * @param {string} song.songId - the id of the song
+	 * @param {number} song.newIndex - the new position for the song in the queue
+	 * @param {number} song.oldIndex - the old position of the song in the queue
+	 * @param {string} stationId - the station id
+	 * @param {Function} cb - callback
 	 */
-	repositionQueue: isOwnerRequired(async function repositionQueue(session, stationId, queue, cb) {
+	repositionSongInQueue: isOwnerRequired(async function repositionQueue(session, song, stationId, cb) {
 		const stationModel = await DBModule.runJob("GET_MODEL", { modelName: "station" }, this);
 
 		async.waterfall(
 			[
 				next => {
-					if (!queue) return next("Invalid queue data.");
-					return StationsModule.runJob("GET_STATION", { stationId }, this)
-						.then(station => next(null, station))
-						.catch(next);
+					if (!song || !song.songId) return next("You must provide a song to reposition.");
+					return next();
 				},
 
-				(station, next) => {
-					if (!station) return next("Station not found.");
-					return stationModel
-						.updateOne({ _id: stationId }, { $set: { queue } }, this)
-						.then(station => next(null, station))
-						.catch(next);
+				// remove song from queue
+				next => {
+					stationModel.updateOne({ _id: stationId }, { $pull: { queue: { songId: song.songId } } }, next);
 				},
 
+				// add song back to queue (in new position)
 				(res, next) => {
+					stationModel.updateOne(
+						{ _id: stationId },
+						{ $push: { queue: { $each: [song], $position: song.newIndex } } },
+						err => next(err)
+					);
+				},
+
+				// update the cache representation of the station
+				next => {
 					StationsModule.runJob("UPDATE_STATION", { stationId }, this)
 						.then(station => next(null, station))
 						.catch(next);
 				}
 			],
 			async err => {
+				console.log(err);
+
 				if (err) {
 					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
 					this.log(
 						"ERROR",
-						"STATIONS_REPOSITION_QUEUE",
-						`Repositioning station "${stationId}" queue failed. "${err}"`
+						"STATIONS_REPOSITION_SONG_IN_QUEUE",
+						`Repositioning song ${song.songId} in queue of station "${stationId}" failed. "${err}"`
 					);
 					return cb({ status: "failure", message: err });
 				}
 
 				this.log(
 					"SUCCESS",
-					"STATIONS_REPOSITION_QUEUE",
-					`Repositioned station "${stationId}" queue successfully.`
+					"STATIONS_REPOSITION_SONG_IN_QUEUE",
+					`Repositioned song ${song.songId} in queue of station "${stationId}" successfully.`
 				);
 
 				CacheModule.runJob("PUB", {
-					channel: "station.queueUpdate",
-					value: stationId
+					channel: "station.repositionSongInQueue",
+					value: {
+						song: {
+							songId: song.songId,
+							oldIndex: song.oldIndex,
+							newIndex: song.newIndex
+						},
+						stationId
+					}
 				});
 
 				return cb({
 					status: "success",
-					message: "Successfully repositioned queue."
+					message: "Successfully repositioned song in queue."
 				});
 			}
 		);

+ 36 - 23
frontend/src/pages/Station/Sidebar/Queue.vue

@@ -10,6 +10,7 @@
 			v-bind="dragOptions"
 			@start="drag = true"
 			@end="drag = false"
+			@change="repositionSongInQueue"
 		>
 			<transition-group
 				type="transition"
@@ -43,14 +44,14 @@
 						<i
 							class="material-icons"
 							v-if="index > 0"
-							@click="moveSongToTop(index)"
+							@click="moveSongToTop(song, index)"
 							content="Move to top of Queue"
 							v-tippy
 							>vertical_align_top</i
 						>
 						<i
 							v-if="queue.length - 1 !== index"
-							@click="moveSongToBottom(index)"
+							@click="moveSongToBottom(song, index)"
 							class="material-icons"
 							content="Move to bottom of Queue"
 							v-tippy
@@ -139,6 +140,14 @@ export default {
 		};
 	},
 	computed: {
+		queue: {
+			get() {
+				return this.$store.state.station.songsList;
+			},
+			set(queue) {
+				this.$store.commit("station/updateSongsList", queue);
+			}
+		},
 		dragOptions() {
 			return {
 				animation: 200,
@@ -147,14 +156,6 @@ export default {
 				ghostClass: "draggable-list-ghost"
 			};
 		},
-		queue: {
-			get() {
-				return this.songsList;
-			},
-			set(queue) {
-				this.updateQueuePositioning(queue);
-			}
-		},
 		...mapState({
 			loggedIn: state => state.user.auth.loggedIn,
 			userId: state => state.user.auth.userId,
@@ -200,27 +201,39 @@ export default {
 				}
 			);
 		},
-		updateQueuePositioning(queue) {
+		repositionSongInQueue({ moved }) {
+			if (!moved) return; // we only need to update when song is moved
+
 			this.socket.dispatch(
-				"stations.repositionQueue",
+				"stations.repositionSongInQueue",
+				{
+					...moved.element,
+					oldIndex: moved.oldIndex,
+					newIndex: moved.newIndex
+				},
 				this.station._id,
-				queue,
 				res => {
 					new Toast({ content: res.message, timeout: 4000 });
 				}
 			);
 		},
-		moveSongToTop(index) {
-			this.queue.splice(0, 0, this.queue.splice(index, 1)[0]);
-			this.updateQueuePositioning(this.queue);
+		moveSongToTop(song, index) {
+			this.repositionSongInQueue({
+				moved: {
+					element: song,
+					oldIndex: index,
+					newIndex: 0
+				}
+			});
 		},
-		moveSongToBottom(index) {
-			this.queue.splice(
-				this.queue.length,
-				0,
-				this.queue.splice(index, 1)[0]
-			);
-			this.updateQueuePositioning(this.queue);
+		moveSongToBottom(song, index) {
+			this.repositionSongInQueue({
+				moved: {
+					element: song,
+					oldIndex: index,
+					newIndex: this.songsList.length
+				}
+			});
 		},
 		...mapActions("modalVisibility", ["openModal"])
 	}

+ 14 - 2
frontend/src/pages/Station/index.vue

@@ -925,10 +925,21 @@ export default {
 
 		this.socket.on("event:queue.update", queue => {
 			this.updateSongsList(queue);
+
 			let nextSong = null;
-			if (this.songsList[0]) {
+			if (this.songsList[0])
 				nextSong = this.songsList[0].songId ? this.songsList[0] : null;
-			}
+
+			this.updateNextSong(nextSong);
+		});
+
+		this.socket.on("event:queue.repositionSong", song => {
+			this.repositionSongInList(song);
+
+			let nextSong = null;
+			if (this.songsList[0])
+				nextSong = this.songsList[0].songId ? this.songsList[0] : null;
+
 			this.updateNextSong(nextSong);
 		});
 
@@ -1929,6 +1940,7 @@ export default {
 			"updatePreviousSong",
 			"updateNextSong",
 			"updateSongsList",
+			"repositionSongInList",
 			"updateStationPaused",
 			"updateLocalPaused",
 			"updateNoSong",

+ 20 - 0
frontend/src/store/modules/station.js

@@ -45,6 +45,9 @@ const actions = {
 	updateSongsList: ({ commit }, songsList) => {
 		commit("updateSongsList", songsList);
 	},
+	repositionSongInList: ({ commit }, song) => {
+		commit("repositionSongInList", song);
+	},
 	updateStationPaused: ({ commit }, stationPaused) => {
 		commit("updateStationPaused", stationPaused);
 	},
@@ -87,6 +90,23 @@ const mutations = {
 	updateSongsList(state, songsList) {
 		state.songsList = songsList;
 	},
+	repositionSongInList(state, song) {
+		if (
+			state.songsList[song.newIndex] &&
+			state.songsList[song.newIndex].songId === song.songId
+		)
+			return;
+
+		const { songsList } = state;
+
+		songsList.splice(
+			song.newIndex,
+			0,
+			songsList.splice(song.oldIndex, 1)[0]
+		);
+
+		state.songsList = songsList;
+	},
 	updateStationPaused(state, stationPaused) {
 		state.stationPaused = stationPaused;
 	},