Browse Source

refactor(WS): on socket reconnect, some pages should be reobtaining data such as indexing etc.

Signed-off-by: Jonathan Graham <theflametrooper@gmail.com>
Jonathan Graham 3 years ago
parent
commit
c03aa584c9
40 changed files with 585 additions and 550 deletions
  1. 0 10
      backend/logic/actions/activities.js
  2. 7 7
      backend/logic/actions/playlists.js
  3. 1 1
      backend/logic/actions/users.js
  4. 7 37
      backend/logic/activities.js
  5. 3 2
      backend/logic/ws.js
  6. 22 22
      frontend/src/App.vue
  7. 14 6
      frontend/src/components/AddToPlaylistDropdown.vue
  8. 9 4
      frontend/src/components/layout/MainHeader.vue
  9. 19 15
      frontend/src/components/modals/EditNews.vue
  10. 0 1
      frontend/src/components/modals/EditPlaylist/Tabs/Settings.vue
  11. 13 9
      frontend/src/components/modals/EditPlaylist/index.vue
  12. 0 1
      frontend/src/components/modals/EditSong/Tabs/Youtube.vue
  13. 218 185
      frontend/src/components/modals/EditSong/index.vue
  14. 13 9
      frontend/src/components/modals/EditUser.vue
  15. 32 25
      frontend/src/components/modals/ManageStation/Tabs/Playlists.vue
  16. 19 11
      frontend/src/components/modals/Report.vue
  17. 17 13
      frontend/src/components/modals/ViewPunishment.vue
  18. 34 29
      frontend/src/components/modals/ViewReport.vue
  19. 30 26
      frontend/src/components/modals/WhatIsNew.vue
  20. 1 6
      frontend/src/pages/About.vue
  21. 1 1
      frontend/src/pages/Admin/index.vue
  22. 4 4
      frontend/src/pages/Admin/tabs/HiddenSongs.vue
  23. 5 6
      frontend/src/pages/Admin/tabs/News.vue
  24. 1 2
      frontend/src/pages/Admin/tabs/Playlists.vue
  25. 2 2
      frontend/src/pages/Admin/tabs/Punishments.vue
  26. 5 6
      frontend/src/pages/Admin/tabs/Reports.vue
  27. 1 2
      frontend/src/pages/Admin/tabs/Stations.vue
  28. 1 2
      frontend/src/pages/Admin/tabs/Statistics.vue
  29. 1 2
      frontend/src/pages/Admin/tabs/UnverifiedSongs.vue
  30. 11 12
      frontend/src/pages/Admin/tabs/Users.vue
  31. 1 2
      frontend/src/pages/Admin/tabs/VerifiedSongs.vue
  32. 1 2
      frontend/src/pages/Home.vue
  33. 5 6
      frontend/src/pages/News.vue
  34. 7 4
      frontend/src/pages/Profile/Tabs/Playlists.vue
  35. 14 26
      frontend/src/pages/Profile/Tabs/RecentActivity.vue
  36. 23 17
      frontend/src/pages/Profile/index.vue
  37. 15 12
      frontend/src/pages/Settings/Tabs/Preferences.vue
  38. 11 6
      frontend/src/pages/Settings/index.vue
  39. 10 5
      frontend/src/pages/Station/Sidebar/Playlists.vue
  40. 7 12
      frontend/src/ws.js

+ 0 - 10
backend/logic/actions/activities.js

@@ -15,11 +15,6 @@ CacheModule.runJob("SUB", {
 		WSModule.runJob("SOCKETS_FROM_USER", { userId }, this).then(sockets =>
 			sockets.forEach(socket => socket.dispatch("event:activity.removeAllForUser"))
 		);
-
-		WSModule.runJob("EMIT_TO_ROOM", {
-			room: `profile-${userId}-activities`,
-			args: ["event:activity.removeAllForUser"]
-		});
 	}
 });
 
@@ -31,11 +26,6 @@ CacheModule.runJob("SUB", {
 		WSModule.runJob("SOCKETS_FROM_USER", { userId }, this).then(sockets =>
 			sockets.forEach(socket => socket.dispatch("event:activity.hidden", { data: { activityId } }))
 		);
-
-		WSModule.runJob("EMIT_TO_ROOM", {
-			room: `profile-${userId}-activities`,
-			args: ["event:activity.hidden", { data: { activityId } }]
-		});
 	}
 });
 

+ 7 - 7
backend/logic/actions/playlists.js

@@ -23,7 +23,7 @@ CacheModule.runJob("SUB", {
 
 		if (playlist.privacy === "public")
 			WSModule.runJob("EMIT_TO_ROOM", {
-				room: `profile-${playlist.createdBy}-playlists`,
+				room: `profile.${playlist.createdBy}.playlists`,
 				args: ["event:playlist.created", { data: { playlist } }]
 			});
 	}
@@ -39,7 +39,7 @@ CacheModule.runJob("SUB", {
 		});
 
 		WSModule.runJob("EMIT_TO_ROOM", {
-			room: `profile-${res.userId}-playlists`,
+			room: `profile.${res.userId}.playlists`,
 			args: ["event:playlist.deleted", { data: { playlistId: res.playlistId } }]
 		});
 	}
@@ -76,7 +76,7 @@ CacheModule.runJob("SUB", {
 
 		if (res.privacy === "public")
 			WSModule.runJob("EMIT_TO_ROOM", {
-				room: `profile-${res.userId}-playlists`,
+				room: `profile.${res.userId}.playlists`,
 				args: [
 					"event:playlist.song.added",
 					{
@@ -106,7 +106,7 @@ CacheModule.runJob("SUB", {
 
 		if (res.privacy === "public")
 			WSModule.runJob("EMIT_TO_ROOM", {
-				room: `profile-${res.userId}-playlists`,
+				room: `profile.${res.userId}.playlists`,
 				args: [
 					"event:playlist.song.removed",
 					{
@@ -136,7 +136,7 @@ CacheModule.runJob("SUB", {
 
 		if (res.privacy === "public")
 			WSModule.runJob("EMIT_TO_ROOM", {
-				room: `profile-${res.userId}-playlists`,
+				room: `profile.${res.userId}.playlists`,
 				args: [
 					"event:playlist.displayName.updated",
 					{
@@ -165,7 +165,7 @@ CacheModule.runJob("SUB", {
 
 		if (res.playlist.privacy === "public")
 			return WSModule.runJob("EMIT_TO_ROOM", {
-				room: `profile-${res.userId}-playlists`,
+				room: `profile.${res.userId}.playlists`,
 				args: [
 					"event:playlist.created",
 					{
@@ -177,7 +177,7 @@ CacheModule.runJob("SUB", {
 			});
 
 		return WSModule.runJob("EMIT_TO_ROOM", {
-			room: `profile-${res.userId}-playlists`,
+			room: `profile.${res.userId}.playlists`,
 			args: [
 				"event:playlist.deleted",
 				{

+ 1 - 1
backend/logic/actions/users.js

@@ -53,7 +53,7 @@ CacheModule.runJob("SUB", {
 		});
 
 		WSModule.runJob("EMIT_TO_ROOM", {
-			room: `profile-${res.userId}-playlists`,
+			room: `profile.${res.userId}.playlists`,
 			args: ["event:user.orderOfPlaylists.updated", { data: { order: res.orderOfPlaylists } }]
 		});
 	}

+ 7 - 37
backend/logic/activities.js

@@ -82,15 +82,6 @@ class _ActivitiesModule extends CoreClass {
 							.catch(next);
 					},
 
-					(activity, next) => {
-						WSModule.runJob("EMIT_TO_ROOM", {
-							room: `profile-${activity.userId}-activities`,
-							args: ["event:activity.created", { data: { activity } }]
-						});
-
-						return next(null, activity);
-					},
-
 					(activity, next) => {
 						const mergeableActivities = ["playlist__remove_song", "playlist__add_song"];
 
@@ -236,11 +227,6 @@ class _ActivitiesModule extends CoreClass {
 								)
 								.catch(next);
 
-							WSModule.runJob("EMIT_TO_ROOM", {
-								room: `profile-${payload.userId}-activities`,
-								args: ["event:activity.hidden", { data: { activityId: activity._id } }]
-							});
-
 							if (activity.type === payload.type) howManySongs += 1;
 							else if (activity.type === `${payload.type}s`)
 								howManySongs += parseInt(
@@ -394,19 +380,6 @@ class _ActivitiesModule extends CoreClass {
 											)
 											.catch(next);
 
-										WSModule.runJob("EMIT_TO_ROOM", {
-											room: `profile-${activity.userId}-activities`,
-											args: [
-												"event:activity.updated",
-												{
-													data: {
-														activityId: activity._id,
-														message: activity.payload.message
-													}
-												}
-											]
-										});
-
 										return next();
 									})
 									.catch(next);
@@ -465,18 +438,15 @@ class _ActivitiesModule extends CoreClass {
 						activities.forEach(activity => {
 							activityModel.updateOne({ _id: activity._id }, { $set: { hidden: true } }).catch(next);
 
-							WSModule.runJob("SOCKETS_FROM_USER", { userId: payload.userId }, this)
-								.then(sockets =>
+							WSModule.runJob("SOCKETS_FROM_USER", { userId: payload.userId })
+								.then(sockets => {
 									sockets.forEach(socket =>
-										socket.dispatch("event:activity.hidden", { data: { activityId: activity._id } })
-									)
-								)
+										socket.dispatch("event:activity.hidden", {
+											data: { activityId: activity._id }
+										})
+									);
+								})
 								.catch(next);
-
-							WSModule.runJob("EMIT_TO_ROOM", {
-								room: `profile-${payload.userId}-activities`,
-								args: ["event:activity.hidden", { data: { activityId: activity._id } }]
-							});
 						});
 
 						return next();

+ 3 - 2
backend/logic/ws.js

@@ -295,8 +295,9 @@ class _WSModule extends CoreClass {
 		const { room, socketId } = payload;
 		return new Promise(resolve => {
 			// create room if it doesn't exist, and add socketId to array
-			if (WSModule.rooms[room]) WSModule.rooms[room].push(socketId);
-			else WSModule.rooms[room] = [socketId];
+			if (WSModule.rooms[room]) {
+				if (!(socketId in WSModule.rooms[room])) WSModule.rooms[room].push(socketId);
+			} else WSModule.rooms[room] = [socketId];
 
 			return resolve();
 		});

+ 22 - 22
frontend/src/App.vue

@@ -143,8 +143,29 @@ export default {
 
 		this.disconnectedMessage.hide();
 
-		ws.onConnect(true, () => {
+		ws.onConnect(() => {
 			this.socketConnected = true;
+
+			this.socket.dispatch("users.getPreferences", res => {
+				if (res.status === "success") {
+					const { preferences } = res.data;
+
+					this.changeAutoSkipDisliked(preferences.autoSkipDisliked);
+					this.changeNightmode(preferences.nightmode);
+					this.changeActivityLogPublic(preferences.activityLogPublic);
+					this.changeAnonymousSongRequests(
+						preferences.anonymousSongRequests
+					);
+					this.changeActivityWatch(preferences.activityWatch);
+
+					if (this.nightmode) this.enableNightmode();
+					else this.disableNightmode();
+				}
+			});
+
+			this.socket.on("keep.event:user.session.deleted", () =>
+				window.location.reload()
+			);
 		});
 
 		ws.onDisconnect(true, () => {
@@ -177,27 +198,6 @@ export default {
 			this.changeNightmode(true);
 			this.enableNightmode();
 		}
-
-		this.socket.dispatch("users.getPreferences", res => {
-			if (res.status === "success") {
-				const { preferences } = res.data;
-
-				this.changeAutoSkipDisliked(preferences.autoSkipDisliked);
-				this.changeNightmode(preferences.nightmode);
-				this.changeActivityLogPublic(preferences.activityLogPublic);
-				this.changeAnonymousSongRequests(
-					preferences.anonymousSongRequests
-				);
-				this.changeActivityWatch(preferences.activityWatch);
-
-				if (this.nightmode) this.enableNightmode();
-				else this.disableNightmode();
-			}
-		});
-
-		this.socket.on("keep.event:user.session.deleted", () =>
-			window.location.reload()
-		);
 	},
 	methods: {
 		toggleNightMode() {

+ 14 - 6
frontend/src/components/AddToPlaylistDropdown.vue

@@ -65,6 +65,7 @@
 <script>
 import { mapGetters, mapState, mapActions } from "vuex";
 import Toast from "toasters";
+import ws from "@/ws";
 
 export default {
 	props: {
@@ -91,12 +92,7 @@ export default {
 		}
 	},
 	mounted() {
-		if (!this.fetchedPlaylists)
-			this.socket.dispatch("playlists.indexMyPlaylists", true, res => {
-				if (res.status === "success")
-					if (!this.fetchedPlaylists)
-						this.setPlaylists(res.data.playlists);
-			});
+		ws.onConnect(this.init);
 
 		this.socket.on(
 			"event:playlist.created",
@@ -124,6 +120,18 @@ export default {
 		);
 	},
 	methods: {
+		init() {
+			if (!this.fetchedPlaylists)
+				this.socket.dispatch(
+					"playlists.indexMyPlaylists",
+					true,
+					res => {
+						if (res.status === "success")
+							if (!this.fetchedPlaylists)
+								this.setPlaylists(res.data.playlists);
+					}
+				);
+		},
 		toggleSongInPlaylist(playlistIndex) {
 			const playlist = this.playlists[playlistIndex];
 			if (!this.hasSong(playlist)) {

+ 9 - 4
frontend/src/components/layout/MainHeader.vue

@@ -77,6 +77,8 @@
 import Toast from "toasters";
 import { mapState, mapGetters, mapActions } from "vuex";
 
+import ws from "@/ws";
+
 export default {
 	props: {
 		hideLogo: { type: Boolean, default: false },
@@ -128,10 +130,7 @@ export default {
 		this.localNightmode = JSON.parse(localStorage.getItem("nightmode"));
 		if (this.localNightmode === null) this.localNightmode = false;
 
-		this.socket.dispatch("users.getPreferences", res => {
-			if (res.status === "success")
-				this.localNightmode = res.data.preferences.nightmode;
-		});
+		ws.onConnect(this.init);
 
 		this.socket.on("keep.event:user.preferences.updated", res => {
 			if (res.data.preferences.nightmode !== undefined)
@@ -143,6 +142,12 @@ export default {
 	},
 
 	methods: {
+		init() {
+			this.socket.dispatch("users.getPreferences", res => {
+				if (res.status === "success")
+					this.localNightmode = res.data.preferences.nightmode;
+			});
+		},
 		...mapActions("modalVisibility", ["openModal"]),
 		...mapActions("user/auth", ["logout"]),
 		...mapActions("user/preferences", ["changeNightmode"])

+ 19 - 15
frontend/src/components/modals/EditNews.vue

@@ -67,6 +67,7 @@ import Toast from "toasters";
 import { formatDistance } from "date-fns";
 
 import UserIdToUsername from "@/components/UserIdToUsername.vue";
+import ws from "@/ws";
 import SaveButton from "../SaveButton.vue";
 import Modal from "../Modal.vue";
 
@@ -101,23 +102,26 @@ export default {
 			}
 		});
 
-		if (this.newsId) {
-			this.socket.dispatch(`news.getNewsFromId`, this.newsId, res => {
-				if (res.status === "success") {
-					const { markdown, status, createdBy, createdAt } =
-						res.data.news;
-					this.markdown = markdown;
-					this.status = status;
-					this.createdBy = createdBy;
-					this.createdAt = createdAt;
-				} else {
-					new Toast("News with that ID not found.");
-					this.closeModal("editNews");
-				}
-			});
-		}
+		ws.onConnect(this.init);
 	},
 	methods: {
+		init() {
+			if (this.newsId) {
+				this.socket.dispatch(`news.getNewsFromId`, this.newsId, res => {
+					if (res.status === "success") {
+						const { markdown, status, createdBy, createdAt } =
+							res.data.news;
+						this.markdown = markdown;
+						this.status = status;
+						this.createdBy = createdBy;
+						this.createdAt = createdAt;
+					} else {
+						new Toast("News with that ID not found.");
+						this.closeModal("editNews");
+					}
+				});
+			}
+		},
 		marked,
 		sanitize,
 		getTitle() {

+ 0 - 1
frontend/src/components/modals/EditPlaylist/Tabs/Settings.vue

@@ -81,7 +81,6 @@ export default {
 			userRole: state => state.user.auth.role
 		})
 	},
-	mounted() {},
 	methods: {
 		isEditable() {
 			return (

+ 13 - 9
frontend/src/components/modals/EditPlaylist/index.vue

@@ -265,6 +265,7 @@ import { mapState, mapGetters, mapActions } from "vuex";
 import draggable from "vuedraggable";
 import Toast from "toasters";
 
+import ws from "@/ws";
 import Confirm from "@/components/Confirm.vue";
 import Modal from "../../Modal.vue";
 import SongItem from "../../SongItem.vue";
@@ -332,15 +333,7 @@ export default {
 		})
 	},
 	mounted() {
-		this.gettingSongs = true;
-		this.socket.dispatch("playlists.getPlaylist", this.editing, res => {
-			if (res.status === "success") {
-				// this.playlist = res.data.playlist;
-				// this.playlist.songs.sort((a, b) => a.position - b.position);
-				this.setPlaylist(res.data.playlist);
-			} else new Toast(res.message);
-			this.gettingSongs = false;
-		});
+		ws.onConnect(this.init);
 
 		this.socket.on(
 			"event:playlist.song.added",
@@ -403,6 +396,17 @@ export default {
 		this.clearPlaylist();
 	},
 	methods: {
+		init() {
+			this.gettingSongs = true;
+			this.socket.dispatch("playlists.getPlaylist", this.editing, res => {
+				if (res.status === "success") {
+					// this.playlist = res.data.playlist;
+					// this.playlist.songs.sort((a, b) => a.position - b.position);
+					this.setPlaylist(res.data.playlist);
+				} else new Toast(res.message);
+				this.gettingSongs = false;
+			});
+		},
 		isEditable() {
 			return (
 				this.playlist.isUserModifiable &&

+ 0 - 1
frontend/src/components/modals/EditSong/Tabs/Youtube.vue

@@ -81,7 +81,6 @@ export default {
 			socket: "websockets/getSocket"
 		})
 	},
-	mounted() {},
 	methods: {
 		...mapActions("modals/editSong", ["updateYoutubeId"])
 	}

+ 218 - 185
frontend/src/components/modals/EditSong/index.vue

@@ -536,6 +536,7 @@ import { mapState, mapGetters, mapActions } from "vuex";
 import Toast from "toasters";
 
 import aw from "@/aw";
+import ws from "@/ws";
 import validation from "@/validation";
 import keyboardShortcuts from "@/keyboardShortcuts";
 
@@ -667,191 +668,7 @@ export default {
 
 		this.useHTTPS = await lofig.get("cookie.secure");
 
-		this.socket.dispatch(`songs.getSongFromSongId`, this.song._id, res => {
-			if (res.status === "success") {
-				const { song } = res.data;
-				// this.song = { ...song };
-				// if (this.song.discogs === undefined)
-				// 	this.song.discogs = null;
-				if (this.song.discogs)
-					this.editSong({ ...song, discogs: this.song.discogs });
-				else this.editSong(song);
-
-				this.songDataLoaded = true;
-
-				this.socket.dispatch(
-					"apis.joinRoom",
-					`edit-song.${this.song._id}`
-				);
-
-				// this.edit(res.data.song);
-
-				this.interval = setInterval(() => {
-					if (
-						this.song.duration !== -1 &&
-						this.video.paused === false &&
-						this.playerReady &&
-						(this.video.player.getCurrentTime() -
-							this.song.skipDuration >
-							this.song.duration ||
-							(this.video.player.getCurrentTime() > 0 &&
-								this.video.player.getCurrentTime() >=
-									this.video.player.getDuration()))
-					) {
-						this.video.paused = true;
-						this.video.player.stopVideo();
-						this.drawCanvas();
-					}
-					if (this.playerReady) {
-						this.youtubeVideoCurrentTime = this.video.player
-							.getCurrentTime()
-							.toFixed(3);
-					}
-
-					if (this.video.paused === false) this.drawCanvas();
-				}, 200);
-
-				if (window.YT && window.YT.Player) {
-					this.video.player = new window.YT.Player("editSongPlayer", {
-						height: 270,
-						width: 480,
-						videoId: this.song.youtubeId,
-						host: "https://www.youtube-nocookie.com",
-						playerVars: {
-							controls: 0,
-							iv_load_policy: 3,
-							rel: 0,
-							showinfo: 0,
-							autoplay: 0
-						},
-						startSeconds: this.song.skipDuration,
-						events: {
-							onReady: () => {
-								let volume = parseInt(
-									localStorage.getItem("volume")
-								);
-								volume =
-									typeof volume === "number" ? volume : 20;
-								this.video.player.setVolume(volume);
-								if (volume > 0) this.video.player.unMute();
-
-								const duration =
-									this.video.player.getDuration();
-
-								this.youtubeVideoDuration = duration.toFixed(3);
-								this.youtubeVideoNote = "(~)";
-								this.playerReady = true;
-
-								this.drawCanvas();
-							},
-							onStateChange: event => {
-								this.drawCanvas();
-
-								let skipToLast10SecsPressed = false;
-								if (
-									event.data === 1 &&
-									this.skipToLast10SecsPressed
-								) {
-									this.skipToLast10SecsPressed = false;
-									skipToLast10SecsPressed = true;
-								}
-
-								if (
-									event.data === 1 &&
-									!skipToLast10SecsPressed
-								) {
-									this.video.paused = false;
-									let youtubeDuration =
-										this.video.player.getDuration();
-									const newYoutubeVideoDuration =
-										youtubeDuration.toFixed(3);
-
-									const songDurationNumber = Number(
-										this.song.duration
-									);
-									const songDurationNumber2 =
-										Number(this.song.duration) + 1;
-									const songDurationNumber3 =
-										Number(this.song.duration) - 1;
-									const fixedSongDuration =
-										songDurationNumber.toFixed(3);
-									const fixedSongDuration2 =
-										songDurationNumber2.toFixed(3);
-									const fixedSongDuration3 =
-										songDurationNumber3.toFixed(3);
-
-									if (
-										this.youtubeVideoDuration !==
-											newYoutubeVideoDuration &&
-										(fixedSongDuration ===
-											this.youtubeVideoDuration ||
-											fixedSongDuration2 ===
-												this.youtubeVideoDuration ||
-											fixedSongDuration3 ===
-												this.youtubeVideoDuration)
-									)
-										this.song.duration =
-											newYoutubeVideoDuration;
-
-									this.youtubeVideoDuration =
-										newYoutubeVideoDuration;
-									this.youtubeVideoNote = "";
-
-									if (this.song.duration === -1)
-										this.song.duration = youtubeDuration;
-
-									youtubeDuration -= this.song.skipDuration;
-									if (
-										this.song.duration >
-										youtubeDuration + 1
-									) {
-										this.video.player.stopVideo();
-										this.video.paused = true;
-										return new Toast(
-											"Video can't play. Specified duration is bigger than the YouTube song duration."
-										);
-									}
-									if (this.song.duration <= 0) {
-										this.video.player.stopVideo();
-										this.video.paused = true;
-										return new Toast(
-											"Video can't play. Specified duration has to be more than 0 seconds."
-										);
-									}
-
-									if (
-										this.video.player.getCurrentTime() <
-										this.song.skipDuration
-									) {
-										return this.video.player.seekTo(
-											this.song.skipDuration
-										);
-									}
-								} else if (event.data === 2) {
-									this.video.paused = true;
-								}
-
-								return false;
-							}
-						}
-					});
-				} else {
-					this.youtubeError = true;
-					this.youtubeErrorMessage = "Player could not be loaded.";
-				}
-			} else {
-				new Toast("Song with that ID not found");
-				this.closeModal("editSong");
-			}
-		});
-
-		this.socket.dispatch(
-			"reports.getReportsForSong",
-			this.song._id,
-			res => {
-				this.updateReports(res.data.reports);
-			}
-		);
+		ws.onConnect(this.init);
 
 		let volume = parseFloat(localStorage.getItem("volume"));
 		volume =
@@ -885,6 +702,7 @@ export default {
 			},
 			{ modal: "editSong" }
 		);
+
 		keyboardShortcuts.registerShortcut("editSong.pauseResumeVideo", {
 			keyCode: 101,
 			preventDefault: true,
@@ -1077,6 +895,221 @@ export default {
 		});
 	},
 	methods: {
+		init() {
+			this.socket.dispatch(
+				`songs.getSongFromSongId`,
+				this.song._id,
+				res => {
+					if (res.status === "success") {
+						const { song } = res.data;
+						// this.song = { ...song };
+						// if (this.song.discogs === undefined)
+						// 	this.song.discogs = null;
+						if (this.song.discogs)
+							this.editSong({
+								...song,
+								discogs: this.song.discogs
+							});
+						else this.editSong(song);
+
+						this.songDataLoaded = true;
+
+						this.socket.dispatch(
+							"apis.joinRoom",
+							`edit-song.${this.song._id}`
+						);
+
+						// this.edit(res.data.song);
+
+						this.interval = setInterval(() => {
+							if (
+								this.song.duration !== -1 &&
+								this.video.paused === false &&
+								this.playerReady &&
+								(this.video.player.getCurrentTime() -
+									this.song.skipDuration >
+									this.song.duration ||
+									(this.video.player.getCurrentTime() > 0 &&
+										this.video.player.getCurrentTime() >=
+											this.video.player.getDuration()))
+							) {
+								this.video.paused = true;
+								this.video.player.stopVideo();
+								this.drawCanvas();
+							}
+							if (this.playerReady) {
+								this.youtubeVideoCurrentTime = this.video.player
+									.getCurrentTime()
+									.toFixed(3);
+							}
+
+							if (this.video.paused === false) this.drawCanvas();
+						}, 200);
+
+						if (window.YT && window.YT.Player) {
+							this.video.player = new window.YT.Player(
+								"editSongPlayer",
+								{
+									height: 270,
+									width: 480,
+									videoId: this.song.youtubeId,
+									host: "https://www.youtube-nocookie.com",
+									playerVars: {
+										controls: 0,
+										iv_load_policy: 3,
+										rel: 0,
+										showinfo: 0,
+										autoplay: 0
+									},
+									startSeconds: this.song.skipDuration,
+									events: {
+										onReady: () => {
+											let volume = parseInt(
+												localStorage.getItem("volume")
+											);
+											volume =
+												typeof volume === "number"
+													? volume
+													: 20;
+											this.video.player.setVolume(volume);
+											if (volume > 0)
+												this.video.player.unMute();
+
+											const duration =
+												this.video.player.getDuration();
+
+											this.youtubeVideoDuration =
+												duration.toFixed(3);
+											this.youtubeVideoNote = "(~)";
+											this.playerReady = true;
+
+											this.drawCanvas();
+										},
+										onStateChange: event => {
+											this.drawCanvas();
+
+											let skipToLast10SecsPressed = false;
+											if (
+												event.data === 1 &&
+												this.skipToLast10SecsPressed
+											) {
+												this.skipToLast10SecsPressed = false;
+												skipToLast10SecsPressed = true;
+											}
+
+											if (
+												event.data === 1 &&
+												!skipToLast10SecsPressed
+											) {
+												this.video.paused = false;
+												let youtubeDuration =
+													this.video.player.getDuration();
+												const newYoutubeVideoDuration =
+													youtubeDuration.toFixed(3);
+
+												const songDurationNumber =
+													Number(this.song.duration);
+												const songDurationNumber2 =
+													Number(this.song.duration) +
+													1;
+												const songDurationNumber3 =
+													Number(this.song.duration) -
+													1;
+												const fixedSongDuration =
+													songDurationNumber.toFixed(
+														3
+													);
+												const fixedSongDuration2 =
+													songDurationNumber2.toFixed(
+														3
+													);
+												const fixedSongDuration3 =
+													songDurationNumber3.toFixed(
+														3
+													);
+
+												if (
+													this
+														.youtubeVideoDuration !==
+														newYoutubeVideoDuration &&
+													(fixedSongDuration ===
+														this
+															.youtubeVideoDuration ||
+														fixedSongDuration2 ===
+															this
+																.youtubeVideoDuration ||
+														fixedSongDuration3 ===
+															this
+																.youtubeVideoDuration)
+												)
+													this.song.duration =
+														newYoutubeVideoDuration;
+
+												this.youtubeVideoDuration =
+													newYoutubeVideoDuration;
+												this.youtubeVideoNote = "";
+
+												if (this.song.duration === -1)
+													this.song.duration =
+														youtubeDuration;
+
+												youtubeDuration -=
+													this.song.skipDuration;
+												if (
+													this.song.duration >
+													youtubeDuration + 1
+												) {
+													this.video.player.stopVideo();
+													this.video.paused = true;
+													return new Toast(
+														"Video can't play. Specified duration is bigger than the YouTube song duration."
+													);
+												}
+												if (this.song.duration <= 0) {
+													this.video.player.stopVideo();
+													this.video.paused = true;
+													return new Toast(
+														"Video can't play. Specified duration has to be more than 0 seconds."
+													);
+												}
+
+												if (
+													this.video.player.getCurrentTime() <
+													this.song.skipDuration
+												) {
+													return this.video.player.seekTo(
+														this.song.skipDuration
+													);
+												}
+											} else if (event.data === 2) {
+												this.video.paused = true;
+											}
+
+											return false;
+										}
+									}
+								}
+							);
+						} else {
+							this.youtubeError = true;
+							this.youtubeErrorMessage =
+								"Player could not be loaded.";
+						}
+					} else {
+						new Toast("Song with that ID not found");
+						this.closeModal("editSong");
+					}
+				}
+			);
+
+			this.socket.dispatch(
+				"reports.getReportsForSong",
+				this.song._id,
+				res => {
+					this.updateReports(res.data.reports);
+				}
+			);
+		},
 		stopEditingSongs() {
 			this.updateEditingSongs(false);
 			this.closeModal("editSong");

+ 13 - 9
frontend/src/components/modals/EditUser.vue

@@ -81,6 +81,7 @@ import { mapState, mapGetters, mapActions } from "vuex";
 
 import Toast from "toasters";
 import validation from "@/validation";
+import ws from "@/ws";
 import Modal from "../Modal.vue";
 
 export default {
@@ -105,17 +106,20 @@ export default {
 		})
 	},
 	mounted() {
-		this.socket.dispatch(`users.getUserFromId`, this.userId, res => {
-			if (res.status === "success") {
-				const user = res.data;
-				this.editUser(user);
-			} else {
-				new Toast("User with that ID not found");
-				this.closeModal("editUser");
-			}
-		});
+		ws.onConnect(this.init);
 	},
 	methods: {
+		init() {
+			this.socket.dispatch(`users.getUserFromId`, this.userId, res => {
+				if (res.status === "success") {
+					const user = res.data;
+					this.editUser(user);
+				} else {
+					new Toast("User with that ID not found");
+					this.closeModal("editUser");
+				}
+			});
+		},
 		updateUsername() {
 			const { username } = this.user;
 			if (!validation.isLength(username, 2, 32))

+ 32 - 25
frontend/src/components/modals/ManageStation/Tabs/Playlists.vue

@@ -637,8 +637,9 @@
 </template>
 <script>
 import { mapActions, mapState, mapGetters } from "vuex";
-
 import Toast from "toasters";
+import ws from "@/ws";
+
 import PlaylistItem from "@/components/PlaylistItem.vue";
 import Confirm from "@/components/Confirm.vue";
 
@@ -704,34 +705,40 @@ export default {
 		if (this.station.type === "community" && this.station.partyMode)
 			this.showTab("search");
 
-		this.socket.dispatch("playlists.indexMyPlaylists", true, res => {
-			if (res.status === "success") this.setPlaylists(res.data.playlists);
-			this.orderOfPlaylists = this.calculatePlaylistOrder(); // order in regards to the database
-		});
+		ws.onConnect(this.init);
+	},
+	methods: {
+		init() {
+			this.socket.dispatch("playlists.indexMyPlaylists", true, res => {
+				if (res.status === "success")
+					this.setPlaylists(res.data.playlists);
+				this.orderOfPlaylists = this.calculatePlaylistOrder(); // order in regards to the database
+			});
 
-		this.socket.dispatch(
-			`stations.getStationIncludedPlaylistsById`,
-			this.station._id,
-			res => {
-				if (res.status === "success") {
-					this.station.includedPlaylists = res.data.playlists;
-					this.originalStation.includedPlaylists = res.data.playlists;
+			this.socket.dispatch(
+				`stations.getStationIncludedPlaylistsById`,
+				this.station._id,
+				res => {
+					if (res.status === "success") {
+						this.station.includedPlaylists = res.data.playlists;
+						this.originalStation.includedPlaylists =
+							res.data.playlists;
+					}
 				}
-			}
-		);
+			);
 
-		this.socket.dispatch(
-			`stations.getStationExcludedPlaylistsById`,
-			this.station._id,
-			res => {
-				if (res.status === "success") {
-					this.station.excludedPlaylists = res.data.playlists;
-					this.originalStation.excludedPlaylists = res.data.playlists;
+			this.socket.dispatch(
+				`stations.getStationExcludedPlaylistsById`,
+				this.station._id,
+				res => {
+					if (res.status === "success") {
+						this.station.excludedPlaylists = res.data.playlists;
+						this.originalStation.excludedPlaylists =
+							res.data.playlists;
+					}
 				}
-			}
-		);
-	},
-	methods: {
+			);
+		},
 		showTab(tab) {
 			this.$refs[`${tab}-tab`].scrollIntoView();
 			this.tab = tab;

+ 19 - 11
frontend/src/components/modals/Report.vue

@@ -207,6 +207,7 @@
 <script>
 import { mapState, mapGetters, mapActions } from "vuex";
 import Toast from "toasters";
+import ws from "@/ws";
 
 import ViewReport from "@/components/modals/ViewReport.vue";
 import SongItem from "@/components/SongItem.vue";
@@ -358,17 +359,7 @@ export default {
 		})
 	},
 	mounted() {
-		this.socket.dispatch("reports.myReportsForSong", this.song._id, res => {
-			if (res.status === "success") {
-				this.existingReports = res.data.reports;
-				this.existingReports.forEach(report =>
-					this.socket.dispatch(
-						"apis.joinRoom",
-						`view-report.${report._id}`
-					)
-				);
-			}
-		});
+		ws.onConnect(this.init);
 
 		this.socket.on(
 			"event:admin.report.resolved",
@@ -381,6 +372,23 @@ export default {
 		);
 	},
 	methods: {
+		init() {
+			this.socket.dispatch(
+				"reports.myReportsForSong",
+				this.song._id,
+				res => {
+					if (res.status === "success") {
+						this.existingReports = res.data.reports;
+						this.existingReports.forEach(report =>
+							this.socket.dispatch(
+								"apis.joinRoom",
+								`view-report.${report._id}`
+							)
+						);
+					}
+				}
+			);
+		},
 		view(reportId) {
 			this.viewReport(reportId);
 			this.openModal("viewReport");

+ 17 - 13
frontend/src/components/modals/ViewPunishment.vue

@@ -62,6 +62,7 @@
 <script>
 import { mapState, mapGetters, mapActions } from "vuex";
 import { format, formatDistance, parseISO } from "date-fns"; // eslint-disable-line no-unused-vars
+import ws from "@/ws";
 
 import Toast from "toasters";
 import Modal from "../Modal.vue";
@@ -87,21 +88,24 @@ export default {
 		})
 	},
 	mounted() {
-		this.socket.dispatch(
-			`punishments.getPunishmentById`,
-			this.punishmentId,
-			res => {
-				if (res.status === "success") {
-					const { punishment } = res.data;
-					this.viewPunishment(punishment);
-				} else {
-					new Toast("Punishment with that ID not found");
-					this.closeModal("viewPunishment");
-				}
-			}
-		);
+		ws.onConnect(this.init);
 	},
 	methods: {
+		init() {
+			this.socket.dispatch(
+				`punishments.getPunishmentById`,
+				this.punishmentId,
+				res => {
+					if (res.status === "success") {
+						const { punishment } = res.data;
+						this.viewPunishment(punishment);
+					} else {
+						new Toast("Punishment with that ID not found");
+						this.closeModal("viewPunishment");
+					}
+				}
+			);
+		},
 		...mapActions("modalVisibility", ["closeModal"]),
 		...mapActions("modals/viewPunishment", ["viewPunishment"]),
 		format,

+ 34 - 29
frontend/src/components/modals/ViewReport.vue

@@ -107,6 +107,7 @@
 <script>
 import { mapActions, mapGetters, mapState } from "vuex";
 import Toast from "toasters";
+import ws from "@/ws";
 
 import Modal from "@/components/Modal.vue";
 import SongItem from "@/components/SongItem.vue";
@@ -140,35 +141,7 @@ export default {
 		})
 	},
 	mounted() {
-		this.socket.dispatch("reports.findOne", this.reportId, res => {
-			if (res.status === "success") {
-				const { report } = res.data;
-
-				this.socket.dispatch(
-					"apis.joinRoom",
-					`view-report.${report._id}`
-				);
-
-				this.report = report;
-
-				this.socket.dispatch(
-					"songs.getSongFromSongId",
-					this.report.song._id,
-					res => {
-						if (res.status === "success") this.song = res.data.song;
-						else {
-							new Toast(
-								"Cannot find the report's associated song"
-							);
-							this.closeModal("viewReport");
-						}
-					}
-				);
-			} else {
-				new Toast("Report with that ID not found");
-				this.closeModal("viewReport");
-			}
-		});
+		ws.onConnect(this.init);
 
 		this.socket.on(
 			"event:admin.report.resolved",
@@ -194,6 +167,38 @@ export default {
 		this.socket.dispatch("apis.leaveRoom", `view-report.${this.reportId}`);
 	},
 	methods: {
+		init() {
+			this.socket.dispatch("reports.findOne", this.reportId, res => {
+				if (res.status === "success") {
+					const { report } = res.data;
+
+					this.socket.dispatch(
+						"apis.joinRoom",
+						`view-report.${report._id}`
+					);
+
+					this.report = report;
+
+					this.socket.dispatch(
+						"songs.getSongFromSongId",
+						this.report.song._id,
+						res => {
+							if (res.status === "success")
+								this.song = res.data.song;
+							else {
+								new Toast(
+									"Cannot find the report's associated song"
+								);
+								this.closeModal("viewReport");
+							}
+						}
+					);
+				} else {
+					new Toast("Report with that ID not found");
+					this.closeModal("viewReport");
+				}
+			});
+		},
 		resolve() {
 			return this.resolveReport(this.reportId)
 				.then(res => {

+ 30 - 26
frontend/src/components/modals/WhatIsNew.vue

@@ -32,6 +32,7 @@ import { formatDistance } from "date-fns";
 import marked from "marked";
 import { sanitize } from "dompurify";
 import { mapGetters, mapActions } from "vuex";
+import ws from "@/ws";
 
 import UserIdToUsername from "@/components/UserIdToUsername.vue";
 import Modal from "../Modal.vue";
@@ -61,34 +62,37 @@ export default {
 			}
 		});
 
-		this.socket.dispatch("news.newest", res => {
-			if (res.status !== "success") return;
-
-			const { news } = res.data;
-
-			this.news = news;
-			if (this.news && localStorage.getItem("firstVisited")) {
-				if (localStorage.getItem("whatIsNew")) {
-					if (
-						parseInt(localStorage.getItem("whatIsNew")) <
-						news.createdAt
-					) {
-						this.openModal("whatIsNew");
-						localStorage.setItem("whatIsNew", news.createdAt);
-					}
-				} else {
-					if (
-						parseInt(localStorage.getItem("firstVisited")) <
-						news.createdAt
-					)
-						this.openModal("whatIsNew");
-					localStorage.setItem("whatIsNew", news.createdAt);
-				}
-			} else if (!localStorage.getItem("firstVisited"))
-				localStorage.setItem("firstVisited", Date.now());
-		});
+		ws.onConnect(this.init);
 	},
 	methods: {
+		init() {
+			this.socket.dispatch("news.newest", res => {
+				if (res.status !== "success") return;
+
+				const { news } = res.data;
+
+				this.news = news;
+				if (this.news && localStorage.getItem("firstVisited")) {
+					if (localStorage.getItem("whatIsNew")) {
+						if (
+							parseInt(localStorage.getItem("whatIsNew")) <
+							news.createdAt
+						) {
+							this.openModal("whatIsNew");
+							localStorage.setItem("whatIsNew", news.createdAt);
+						}
+					} else {
+						if (
+							parseInt(localStorage.getItem("firstVisited")) <
+							news.createdAt
+						)
+							this.openModal("whatIsNew");
+						localStorage.setItem("whatIsNew", news.createdAt);
+					}
+				} else if (!localStorage.getItem("firstVisited"))
+					localStorage.setItem("firstVisited", Date.now());
+			});
+		},
 		marked,
 		sanitize,
 		formatDistance,

+ 1 - 6
frontend/src/pages/About.vue

@@ -70,12 +70,7 @@ import MainHeader from "@/components/layout/MainHeader.vue";
 import MainFooter from "@/components/layout/MainFooter.vue";
 
 export default {
-	components: { MainHeader, MainFooter },
-	data() {
-		return {};
-	},
-	mounted() {},
-	methods: {}
+	components: { MainHeader, MainFooter }
 };
 </script>
 

+ 1 - 1
frontend/src/pages/Admin/index.vue

@@ -176,7 +176,7 @@ export default {
 		this.changeTab(this.$route.path);
 	},
 	beforeUnmount() {
-		this.socket.dispatch("apis.leaveRooms", () => {});
+		this.socket.dispatch("apis.leaveRooms");
 	},
 	methods: {
 		changeTab(path) {

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

@@ -230,7 +230,10 @@ export default {
 		})
 	},
 	mounted() {
+		ws.onConnect(this.init);
+
 		this.socket.on("event:admin.hiddenSong.created", res => {
+			console.log("CREATED");
 			this.addSong(res.data.song);
 		});
 
@@ -241,9 +244,6 @@ export default {
 		this.socket.on("event:admin.hiddenSong.updated", res => {
 			this.updateSong(res.data.song);
 		});
-
-		if (this.socket.readyState === 1) this.init();
-		ws.onConnect(() => this.init());
 	},
 	methods: {
 		edit(song) {
@@ -306,7 +306,7 @@ export default {
 				return new Toast(`Error: ${res.mesage}`);
 			});
 
-			this.socket.dispatch("apis.joinAdminRoom", "hiddenSongs", () => {});
+			this.socket.dispatch("apis.joinAdminRoom", "hiddenSongs");
 		},
 		...mapActions("admin/hiddenSongs", [
 			// "stopVideo",

+ 5 - 6
frontend/src/pages/Admin/tabs/News.vue

@@ -89,10 +89,6 @@ export default {
 		})
 	},
 	mounted() {
-		this.socket.dispatch("news.index", res => {
-			if (res.status === "success") this.setNews(res.data.news);
-		});
-
 		this.socket.on("event:admin.news.created", res =>
 			this.addNews(res.data.news)
 		);
@@ -105,8 +101,7 @@ export default {
 			this.removeNews(res.data.newsId)
 		);
 
-		if (this.socket.readyState === 1) this.init();
-		ws.onConnect(() => this.init());
+		ws.onConnect(this.init);
 	},
 	methods: {
 		edit(id) {
@@ -122,6 +117,10 @@ export default {
 			);
 		},
 		init() {
+			this.socket.dispatch("news.index", res => {
+				if (res.status === "success") this.setNews(res.data.news);
+			});
+
 			this.socket.dispatch("apis.joinAdminRoom", "news");
 		},
 		...mapActions("modalVisibility", ["openModal", "closeModal"]),

+ 1 - 2
frontend/src/pages/Admin/tabs/Playlists.vue

@@ -128,8 +128,7 @@ export default {
 		})
 	},
 	mounted() {
-		if (this.socket.readyState === 1) this.init();
-		ws.onConnect(() => this.init());
+		ws.onConnect(this.init);
 	},
 	methods: {
 		edit(playlistId) {

+ 2 - 2
frontend/src/pages/Admin/tabs/Punishments.vue

@@ -128,8 +128,7 @@ export default {
 		})
 	},
 	mounted() {
-		if (this.socket.readyState === 1) this.init();
-		ws.onConnect(() => this.init());
+		ws.onConnect(this.init);
 
 		this.socket.on("event:admin.punishment.created", res =>
 			this.punishments.push(res.data.punishment)
@@ -157,6 +156,7 @@ export default {
 				if (res.status === "success")
 					this.punishments = res.data.punishments;
 			});
+
 			this.socket.dispatch("apis.joinAdminRoom", "punishments", () => {});
 		},
 		...mapActions("modalVisibility", ["openModal"]),

+ 5 - 6
frontend/src/pages/Admin/tabs/Reports.vue

@@ -116,12 +116,7 @@ export default {
 		})
 	},
 	mounted() {
-		if (this.socket.readyState === 1) this.init();
-		ws.onConnect(() => this.init());
-
-		this.socket.dispatch("reports.index", res => {
-			if (res.status === "success") this.reports = res.data.reports;
-		});
+		ws.onConnect(this.init);
 
 		this.socket.on("event:admin.report.resolved", res => {
 			this.reports = this.reports.filter(
@@ -146,6 +141,10 @@ export default {
 	},
 	methods: {
 		init() {
+			this.socket.dispatch("reports.index", res => {
+				if (res.status === "success") this.reports = res.data.reports;
+			});
+
 			this.socket.dispatch("apis.joinAdminRoom", "reports", () => {});
 		},
 		getCategories(issues) {

+ 1 - 2
frontend/src/pages/Admin/tabs/Stations.vue

@@ -244,8 +244,7 @@ export default {
 		})
 	},
 	mounted() {
-		if (this.socket.readyState === 1) this.init();
-		ws.onConnect(() => this.init());
+		ws.onConnect(this.init);
 
 		this.socket.on("event:admin.station.created", res =>
 			this.stationAdded(res.data.station)

+ 1 - 2
frontend/src/pages/Admin/tabs/Statistics.vue

@@ -228,8 +228,7 @@ export default {
 		socket: "websockets/getSocket"
 	}),
 	mounted() {
-		if (this.socket.readyState === 1) this.init();
-		ws.onConnect(() => this.init());
+		ws.onConnect(this.init);
 	},
 	methods: {
 		init() {

+ 1 - 2
frontend/src/pages/Admin/tabs/UnverifiedSongs.vue

@@ -260,8 +260,7 @@ export default {
 			this.updateSong(res.data.song);
 		});
 
-		if (this.socket.readyState === 1) this.init();
-		ws.onConnect(() => this.init());
+		ws.onConnect(this.init);
 	},
 	methods: {
 		edit(song) {

+ 11 - 12
frontend/src/pages/Admin/tabs/Users.vue

@@ -130,8 +130,17 @@ export default {
 		})
 	},
 	mounted() {
-		if (this.socket.readyState === 1) this.init();
-		ws.onConnect(() => this.init());
+		ws.onConnect(this.init);
+
+		this.socket.on("event:admin.dataRequests.created", res =>
+			this.dataRequests.push(res.data.request)
+		);
+
+		this.socket.on("event:admin.dataRequests.resolved", res => {
+			this.dataRequests = this.dataRequests.filter(
+				request => request._id !== res.data.dataRequestId
+			);
+		});
 	},
 	methods: {
 		edit(user) {
@@ -157,16 +166,6 @@ export default {
 			});
 
 			this.socket.dispatch("apis.joinAdminRoom", "users", () => {});
-
-			this.socket.on("event:admin.dataRequests.created", res =>
-				this.dataRequests.push(res.data.request)
-			);
-
-			this.socket.on("event:admin.dataRequests.resolved", res => {
-				this.dataRequests = this.dataRequests.filter(
-					request => request._id !== res.data.dataRequestId
-				);
-			});
 		},
 		resolveDataRequest(id) {
 			this.socket.dispatch("dataRequests.resolve", id, res => {

+ 1 - 2
frontend/src/pages/Admin/tabs/VerifiedSongs.vue

@@ -392,8 +392,7 @@ export default {
 			this.updateSong(res.data.song)
 		);
 
-		if (this.socket.readyState === 1) this.init();
-		ws.onConnect(() => this.init());
+		ws.onConnect(this.init);
 
 		if (this.$route.query.songId) {
 			this.socket.dispatch(

+ 1 - 2
frontend/src/pages/Home.vue

@@ -522,8 +522,7 @@ export default {
 	async mounted() {
 		this.sitename = await lofig.get("siteSettings.sitename");
 
-		if (this.socket.readyState === 1) this.init();
-		ws.onConnect(() => this.init());
+		ws.onConnect(this.init);
 
 		this.socket.on("event:station.created", res => {
 			const { station } = res.data;

+ 5 - 6
frontend/src/pages/News.vue

@@ -72,10 +72,6 @@ export default {
 			}
 		});
 
-		this.socket.dispatch("news.index", res => {
-			if (res.status === "success") this.news = res.data.news;
-		});
-
 		this.socket.on("event:news.created", res =>
 			this.news.unshift(res.data.news)
 		);
@@ -101,14 +97,17 @@ export default {
 			this.news = this.news.filter(item => item._id !== res.data.newsId);
 		});
 
-		if (this.socket.readyState === 1) this.init();
-		ws.onConnect(() => this.init());
+		ws.onConnect(this.init);
 	},
 	methods: {
 		marked,
 		sanitize,
 		formatDistance,
 		init() {
+			this.socket.dispatch("news.index", res => {
+				if (res.status === "success") this.news = res.data.news;
+			});
+
 			this.socket.dispatch("apis.joinRoom", "news");
 		}
 	}

+ 7 - 4
frontend/src/pages/Profile/Tabs/Playlists.vue

@@ -136,10 +136,13 @@ export default {
 			);
 		}
 
-		this.socket.dispatch("playlists.indexForUser", this.userId, res => {
-			if (res.status === "success") this.setPlaylists(res.data.playlists);
-			this.orderOfPlaylists = this.calculatePlaylistOrder(); // order in regards to the database
-		});
+		ws.onConnect(() =>
+			this.socket.dispatch("playlists.indexForUser", this.userId, res => {
+				if (res.status === "success")
+					this.setPlaylists(res.data.playlists);
+				this.orderOfPlaylists = this.calculatePlaylistOrder(); // order in regards to the database
+			})
+		);
 	},
 	beforeUnmount() {
 		this.socket.dispatch(

+ 14 - 26
frontend/src/pages/Profile/Tabs/RecentActivity.vue

@@ -77,25 +77,7 @@ export default {
 		})
 	},
 	mounted() {
-		if (this.myUserId !== this.userId) {
-			ws.onConnect(() =>
-				this.socket.dispatch(
-					"apis.joinRoom",
-					`profile.${this.userId}.activities`
-				)
-			);
-
-			this.getUsernameFromId(this.userId).then(username => {
-				if (username) this.username = username;
-			});
-		}
-
-		this.socket.dispatch("activities.length", this.userId, res => {
-			if (res.status === "success") {
-				this.maxPosition = Math.ceil(res.data.length / 15) + 1;
-				this.getSet();
-			}
-		});
+		ws.onConnect(this.init);
 
 		this.socket.on("event:activity.updated", res => {
 			this.activities.find(
@@ -123,14 +105,20 @@ export default {
 			this.offsettedFromNextSet = 0;
 		});
 	},
-	beforeUnmount() {
-		this.socket.dispatch(
-			"apis.leaveRoom",
-			`profile.${this.userId}.activities`,
-			() => {}
-		);
-	},
 	methods: {
+		init() {
+			if (this.myUserId !== this.userId)
+				this.getUsernameFromId(this.userId).then(username => {
+					if (username) this.username = username;
+				});
+
+			this.socket.dispatch("activities.length", this.userId, res => {
+				if (res.status === "success") {
+					this.maxPosition = Math.ceil(res.data.length / 15) + 1;
+					this.getSet();
+				}
+			});
+		},
 		hideActivity(activityId) {
 			this.socket.dispatch("activities.hideActivity", activityId, res => {
 				if (res.status !== "success") new Toast(res.message);

+ 23 - 17
frontend/src/pages/Profile/index.vue

@@ -109,6 +109,7 @@
 import { mapState, mapGetters } from "vuex";
 import { format, parseISO } from "date-fns";
 import { defineAsyncComponent } from "vue";
+import ws from "@/ws";
 
 import TabQueryHandler from "@/mixins/TabQueryHandler.vue";
 
@@ -167,24 +168,29 @@ export default {
 		)
 			this.tab = this.$route.query.tab;
 
-		this.socket.dispatch(
-			"users.findByUsername",
-			this.$route.params.username,
-			res => {
-				if (res.status === "error") this.$router.push("/404");
-				else {
-					this.user = res.data;
-
-					this.user.createdAt = format(
-						parseISO(this.user.createdAt),
-						"MMMM do yyyy"
-					);
-
-					this.isUser = true;
-					this.userId = this.user._id;
+		ws.onConnect(this.init);
+	},
+	methods: {
+		init() {
+			this.socket.dispatch(
+				"users.findByUsername",
+				this.$route.params.username,
+				res => {
+					if (res.status === "error") this.$router.push("/404");
+					else {
+						this.user = res.data;
+
+						this.user.createdAt = format(
+							parseISO(this.user.createdAt),
+							"MMMM do yyyy"
+						);
+
+						this.isUser = true;
+						this.userId = this.user._id;
+					}
 				}
-			}
-		);
+			);
+		}
 	}
 };
 </script>

+ 15 - 12
frontend/src/pages/Settings/Tabs/Preferences.vue

@@ -90,6 +90,7 @@
 <script>
 import { mapState, mapActions, mapGetters } from "vuex";
 import Toast from "toasters";
+import ws from "@/ws";
 
 import SaveButton from "@/components/SaveButton.vue";
 
@@ -119,18 +120,20 @@ export default {
 		})
 	},
 	mounted() {
-		this.socket.dispatch("users.getPreferences", res => {
-			const { preferences } = res.data;
-
-			if (res.status === "success") {
-				this.localNightmode = preferences.nightmode;
-				this.localAutoSkipDisliked = preferences.autoSkipDisliked;
-				this.localActivityLogPublic = preferences.activityLogPublic;
-				this.localAnonymousSongRequests =
-					preferences.anonymousSongRequests;
-				this.localActivityWatch = preferences.activityWatch;
-			}
-		});
+		ws.onConnect(() =>
+			this.socket.dispatch("users.getPreferences", res => {
+				const { preferences } = res.data;
+
+				if (res.status === "success") {
+					this.localNightmode = preferences.nightmode;
+					this.localAutoSkipDisliked = preferences.autoSkipDisliked;
+					this.localActivityLogPublic = preferences.activityLogPublic;
+					this.localAnonymousSongRequests =
+						preferences.anonymousSongRequests;
+					this.localActivityWatch = preferences.activityWatch;
+				}
+			})
+		);
 
 		this.socket.on("keep.event:user.preferences.updated", res => {
 			const { preferences } = res.data;

+ 11 - 6
frontend/src/pages/Settings/index.vue

@@ -50,8 +50,8 @@
 <script>
 import { mapActions, mapGetters, mapState } from "vuex";
 import { defineAsyncComponent } from "vue";
-
 import Toast from "toasters";
+import ws from "@/ws";
 
 import TabQueryHandler from "@/mixins/TabQueryHandler.vue";
 
@@ -104,10 +104,7 @@ export default {
 
 		this.localNightmode = this.nightmode;
 
-		this.socket.dispatch("users.findBySession", res => {
-			if (res.status === "success") this.setUser(res.data.user);
-			else new Toast("You're not currently signed in.");
-		});
+		ws.onConnect(this.init);
 
 		this.socket.on("event:user.password.linked", () =>
 			this.updateOriginalUser({
@@ -137,7 +134,15 @@ export default {
 			})
 		);
 	},
-	methods: mapActions("settings", ["updateOriginalUser", "setUser"])
+	methods: {
+		init() {
+			this.socket.dispatch("users.findBySession", res => {
+				if (res.status === "success") this.setUser(res.data.user);
+				else new Toast("You're not currently signed in.");
+			});
+		},
+		...mapActions("settings", ["updateOriginalUser", "setUser"])
+	}
 };
 </script>
 

+ 10 - 5
frontend/src/pages/Station/Sidebar/Playlists.vue

@@ -102,6 +102,7 @@
 <script>
 import { mapState, mapActions, mapGetters } from "vuex";
 import Toast from "toasters";
+import ws from "@/ws";
 
 import PlaylistItem from "@/components/PlaylistItem.vue";
 import SortablePlaylists from "@/mixins/SortablePlaylists.vue";
@@ -133,11 +134,7 @@ export default {
 		})
 	},
 	mounted() {
-		/** Get playlists for user */
-		this.socket.dispatch("playlists.indexMyPlaylists", true, res => {
-			if (res.status === "success") this.setPlaylists(res.data.playlists);
-			this.orderOfPlaylists = this.calculatePlaylistOrder(); // order in regards to the database
-		});
+		ws.onConnect(this.init);
 
 		this.socket.on("event:station.includedPlaylist", res => {
 			const { playlist } = res.data;
@@ -174,6 +171,14 @@ export default {
 		});
 	},
 	methods: {
+		init() {
+			/** Get playlists for user */
+			this.socket.dispatch("playlists.indexMyPlaylists", true, res => {
+				if (res.status === "success")
+					this.setPlaylists(res.data.playlists);
+				this.orderOfPlaylists = this.calculatePlaylistOrder(); // order in regards to the database
+			});
+		},
 		isOwner() {
 			return this.loggedIn && this.userId === this.station.owner;
 		},

+ 7 - 12
frontend/src/ws.js

@@ -2,10 +2,7 @@
 import store from "./store";
 import ListenerHandler from "./classes/ListenerHandler.class";
 
-const onConnect = {
-	temp: [],
-	persist: []
-};
+const onConnect = [];
 
 let pendingDispatches = [];
 
@@ -22,9 +19,10 @@ export default {
 	socket: null,
 	dispatcher: null,
 
-	onConnect(...args) {
-		if (args[0] === true) onConnect.persist.push(args[1]);
-		else onConnect.temp.push(args[0]);
+	onConnect(cb) {
+		if (this.socket.readyState === 1) cb();
+
+		return onConnect.push(cb);
 	},
 
 	onDisconnect(...args) {
@@ -33,7 +31,6 @@ export default {
 	},
 
 	clearCallbacks: () => {
-		onConnect.temp = [];
 		onDisconnect.temp = [];
 	},
 
@@ -111,14 +108,12 @@ export default {
 			console.log("WS: SOCKET CONNECTED");
 
 			setTimeout(() => {
-				onConnect.temp.forEach(cb => cb());
+				onConnect.forEach(cb => cb());
 
 				// dispatches that were attempted while the server was offline
 				pendingDispatches.forEach(cb => cb());
 				pendingDispatches = [];
-
-				onConnect.persist.forEach(cb => cb());
-			}, 50); // small delay between readyState being 1 and the server actually receiving dispatches
+			}, 150); // small delay between readyState being 1 and the server actually receiving dispatches
 		};
 
 		this.socket.onmessage = message => {