Przeglądaj źródła

feat(Activities): added 'scroll and fetch' functionality on Profile page

Signed-off-by: Jonathan <theflametrooper@gmail.com>
Jonathan 4 lat temu
rodzic
commit
0eff63cc71

+ 37 - 2
backend/logic/actions/activities.js

@@ -38,15 +38,50 @@ CacheModule.runJob("SUB", {
 });
 
 export default {
+	/**
+	 * Returns how many activities there are for a user
+	 *
+	 * @param {object} session - the session object automatically added by socket.io
+	 * @param {string} userId - the id of the user in question
+	 * @param {Function} cb - callback
+	 */
+	async length(session, userId, cb) {
+		const activityModel = await DBModule.runJob("GET_MODEL", { modelName: "activity" }, this);
+
+		async.waterfall(
+			[
+				next => {
+					activityModel.countDocuments({ userId, hidden: false }, next);
+				}
+			],
+			async (err, count) => {
+				if (err) {
+					err = await UtilsModule.runJob("GET_ERROR", { error: err }, this);
+					this.log(
+						"ERROR",
+						"SONGS_LENGTH",
+						`Failed to get length of activities for user ${userId}. "${err}"`
+					);
+					return cb({ status: "failure", message: err });
+				}
+
+				this.log("SUCCESS", "ACTIVITIES_LENGTH", `Got length of activities for user ${userId} successfully.`);
+
+				return cb(count);
+			}
+		);
+	},
+
 	/**
 	 * Gets a set of activities
 	 *
 	 * @param {object} session - user session
 	 * @param {string} userId - the user whose activities we are looking for
 	 * @param {number} set - the set number to return
+	 * @param {number} offset - how many activities to skip (keeps frontend and backend in sync)
 	 * @param {Function} cb - callback
 	 */
-	async getSet(session, userId, set, cb) {
+	async getSet(session, userId, set, offset, cb) {
 		const userModel = await DBModule.runJob("GET_MODEL", { modelName: "user" }, this);
 		const activityModel = await DBModule.runJob("GET_MODEL", { modelName: "activity" }, this);
 
@@ -74,7 +109,7 @@ export default {
 				next => {
 					activityModel
 						.find({ userId, hidden: false })
-						.skip(15 * (set - 1))
+						.skip(15 * (set - 1) + offset)
 						.limit(15)
 						.sort({ createdAt: -1 })
 						.exec(next);

+ 1 - 1
frontend/src/mixins/ScrollAndFetchHandler.vue

@@ -4,7 +4,7 @@ export default {
 		return {
 			position: 1,
 			maxPosition: 1,
-			gettingSet: false,
+			isGettingSet: false,
 			loadAllSongs: false,
 			interval: null
 		};

+ 4 - 4
frontend/src/pages/Admin/tabs/QueueSongs.vue

@@ -1,5 +1,5 @@
 <template>
-	<div v-scroll="handleScroll">
+	<div @scroll="handleScroll">
 		<metadata title="Admin | Queue songs" />
 		<div class="container">
 			<p>
@@ -280,15 +280,15 @@ export default {
 			});
 		},
 		getSet() {
-			if (this.gettingSet) return;
+			if (this.isGettingSet) return;
 			if (this.position >= this.maxPosition) return;
-			this.gettingSet = true;
+			this.isGettingSet = true;
 
 			this.socket.emit("queueSongs.getSet", this.position, data => {
 				data.forEach(song => this.songs.push(song));
 
 				this.position += 1;
-				this.gettingSet = false;
+				this.isGettingSet = false;
 			});
 		},
 		selectPrevious(event) {

+ 4 - 4
frontend/src/pages/Admin/tabs/Songs.vue

@@ -1,7 +1,7 @@
 <template>
 	<div>
 		<metadata title="Admin | Songs" />
-		<div class="container" v-scroll="handleScroll">
+		<div class="container" @scroll="handleScroll">
 			<p>
 				<span>Sets loaded: {{ setsLoaded }} / {{ maxSets }}</span>
 				<br />
@@ -363,9 +363,9 @@ export default {
 			});
 		},
 		getSet() {
-			if (this.gettingSet) return;
+			if (this.isGettingSet) return;
 			if (this.position >= this.maxPosition) return;
-			this.gettingSet = true;
+			this.isGettingSet = true;
 
 			this.socket.emit("songs.getSet", this.position, data => {
 				data.forEach(song => {
@@ -373,7 +373,7 @@ export default {
 				});
 
 				this.position += 1;
-				this.gettingSet = false;
+				this.isGettingSet = false;
 			});
 		},
 		toggleArtistSelected(artist) {

+ 89 - 45
frontend/src/pages/Profile/tabs/RecentActivity.vue

@@ -11,22 +11,26 @@
 
 			<hr class="section-horizontal-rule" />
 
-			<activity-item
-				class="item activity-item universal-item"
-				v-for="activity in sortedActivities"
-				:key="activity._id"
-				:activity="activity"
-			>
-				<div slot="actions">
-					<a
-						v-if="userId === myUserId"
-						href="#"
-						@click.prevent="hideActivity(activity._id)"
-					>
-						<i class="material-icons hide-icon">visibility_off</i>
-					</a>
-				</div>
-			</activity-item>
+			<div id="activity-items" @scroll="handleScroll">
+				<activity-item
+					class="item activity-item universal-item"
+					v-for="activity in activities"
+					:key="activity._id"
+					:activity="activity"
+				>
+					<div slot="actions">
+						<a
+							v-if="userId === myUserId"
+							href="#"
+							@click.prevent="hideActivity(activity._id)"
+						>
+							<i class="material-icons hide-icon"
+								>visibility_off</i
+							>
+						</a>
+					</div>
+				</activity-item>
+			</div>
 		</div>
 		<div v-else>
 			<h3>No recent activity.</h3>
@@ -35,7 +39,7 @@
 </template>
 
 <script>
-import { mapState, mapActions } from "vuex";
+import { mapState } from "vuex";
 import Toast from "toasters";
 
 import io from "../../../io";
@@ -50,15 +54,17 @@ export default {
 			default: ""
 		}
 	},
+	data() {
+		return {
+			activities: [],
+			position: 1,
+			maxPosition: 1,
+			offsettedFromNextSet: 0,
+			isGettingSet: false
+		};
+	},
 	computed: {
-		sortedActivities() {
-			const { activities } = this;
-			return activities.sort(
-				(x, y) => new Date(y.createdAt) - new Date(x.createdAt)
-			);
-		},
 		...mapState({
-			activities: state => state.user.activities.activities,
 			...mapState("modalVisibility", {
 				modals: state => state.modals.station
 			}),
@@ -78,25 +84,30 @@ export default {
 				);
 			}
 
-			this.socket.emit("activities.getSet", this.userId, 1, res => {
-				if (res.status === "success")
-					this.addSetOfActivities({
-						activities: res.data,
-						set: 1
-					});
+			this.socket.emit("activities.length", this.userId, length => {
+				this.maxPosition = Math.ceil(length / 15) + 1;
+				this.getSet();
 			});
 
-			this.socket.on("event:activity.create", activity =>
-				this.addActivity(activity)
-			);
+			this.socket.on("event:activity.create", activity => {
+				this.activities.unshift(activity);
+				this.offsettedFromNextSet += 1;
+			});
 
-			this.socket.on("event:activity.hide", activityId =>
-				this.removeActivity(activityId)
-			);
+			this.socket.on("event:activity.hide", activityId => {
+				this.activities = this.activities.filter(
+					activity => activity._id !== activityId
+				);
 
-			this.socket.on("event:activity.removeAllForUser", () =>
-				this.removeAllActivities()
-			);
+				this.offsettedFromNextSet -= 1;
+			});
+
+			this.socket.on("event:activity.removeAllForUser", () => {
+				this.activities = [];
+				this.position = 1;
+				this.maxPosition = 1;
+				this.offsettedFromNextSet = 0;
+			});
 		});
 	},
 	methods: {
@@ -106,12 +117,45 @@ export default {
 					new Toast({ content: res.message, timeout: 3000 });
 			});
 		},
-		...mapActions("user/activities", [
-			"addSetOfActivities",
-			"addActivity",
-			"removeActivity",
-			"removeAllActivities"
-		])
+		getSet() {
+			if (this.isGettingSet) return;
+			if (this.position >= this.maxPosition) return;
+
+			this.isGettingSet = true;
+
+			this.socket.emit(
+				"activities.getSet",
+				this.userId,
+				this.position,
+				this.offsettedFromNextSet,
+				res => {
+					if (res.status === "success") {
+						this.activities.push(...res.data);
+						this.position += 1;
+					}
+
+					this.isGettingSet = false;
+				}
+			);
+		},
+		handleScroll(event) {
+			const scrollPosition =
+				event.target.clientHeight + event.target.scrollTop;
+			const bottomPosition = event.target.scrollHeight;
+
+			if (this.loadAllSongs) return false;
+
+			if (scrollPosition + 100 >= bottomPosition) this.getSet();
+
+			return this.maxPosition === this.position;
+		}
 	}
 };
 </script>
+
+<style lang="scss" scoped>
+#activity-items {
+	overflow: auto;
+	height: 600px;
+}
+</style>

+ 0 - 46
frontend/src/store/modules/user.js

@@ -199,52 +199,6 @@ const modules = {
 			}
 		}
 	},
-	activities: {
-		namespaced: true,
-		state: {
-			activities: [],
-			position: 0,
-			maxPosition: 1,
-			offsettedFromNextSet: 0
-		},
-		actions: {
-			addSetOfActivities: ({ commit }, data) =>
-				commit("addSetOfActivities", data),
-			addActivity: ({ commit }, activity) =>
-				commit("addActivity", activity),
-			removeActivity: ({ commit }, activityId) =>
-				commit("removeActivity", activityId),
-			removeAllActivities: ({ commit }) => commit("removeAllActivities")
-		},
-		mutations: {
-			addActivity(state, activity) {
-				state.activities.unshift(activity);
-				state.offsettedFromNextSet += 1;
-			},
-			addSetOfActivities(state, data) {
-				const { activities, set } = data;
-
-				if (set > state.position && set <= state.maxPosition) {
-					state.activities.push(...activities);
-					state.position = set;
-				} else {
-					state.activities = activities;
-					state.position = set;
-				}
-			},
-			removeActivity(state, activityId) {
-				state.activities = state.activities.filter(
-					activity => activity._id !== activityId
-				);
-			},
-			removeAllActivities(state) {
-				state.activities = [];
-				state.position = 0;
-				state.maxPosition = 1;
-				state.offsettedFromNextSet = 0;
-			}
-		}
-	},
 	playlists: {
 		namespaced: true,
 		state: {