Browse Source

refactor: Consolidate playlist tabs in station and manage station

Owen Diffey 3 years ago
parent
commit
3b15918f74

+ 471 - 227
frontend/src/components/modals/ManageStation/Tabs/Autofill.vue → frontend/src/components/PlaylistTabBase.vue

@@ -1,5 +1,8 @@
 <template>
-	<div class="station-playlists">
+	<div class="playlist-tab-base">
+		<div v-if="$slots.info" class="top-info has-text-centered">
+			<slot name="info" />
+		</div>
 		<div class="tabs-container">
 			<div class="tab-selection">
 				<button
@@ -39,48 +42,78 @@
 					>
 						<template #item-icon>
 							<i
-								class="material-icons"
-								v-if="isIncluded(featuredPlaylist._id)"
-								content="This playlist is currently included"
+								class="material-icons blacklisted-icon"
+								v-if="
+									isSelected(
+										featuredPlaylist._id,
+										'blacklist'
+									)
+								"
+								:content="`This playlist is currently ${label(
+									'past',
+									'blacklist'
+								)}`"
 								v-tippy
 							>
-								play_arrow
+								block
 							</i>
 							<i
-								class="material-icons blacklisted-icon"
-								v-else-if="isBlacklisted(featuredPlaylist._id)"
-								content="This playlist is currently blacklisted"
+								class="material-icons"
+								v-else-if="isSelected(featuredPlaylist._id)"
+								:content="`This playlist is currently ${label(
+									'past'
+								)}`"
 								v-tippy
 							>
-								block
+								play_arrow
 							</i>
 							<i
 								class="material-icons"
 								v-else
-								content="This playlist is currently not included or blacklisted"
+								:content="`This playlist is currently not ${label(
+									'past'
+								)}`"
 								v-tippy
 							>
-								play_disabled
+								{{
+									type === "blacklist"
+										? "block"
+										: "play_disabled"
+								}}
 							</i>
 						</template>
 
 						<template #actions>
 							<i
-								v-if="isBlacklisted(featuredPlaylist._id)"
+								v-if="
+									type !== 'blacklist' &&
+									isSelected(
+										featuredPlaylist._id,
+										'blacklist'
+									)
+								"
 								class="material-icons stop-icon"
-								content="This playlist is blacklisted in this station"
+								:content="`This playlist is ${label(
+									'past',
+									'blacklist'
+								)} in this station`"
 								v-tippy="{ theme: 'info' }"
 								>play_disabled</i
 							>
 							<quick-confirm
-								v-if="isIncluded(featuredPlaylist._id)"
+								v-if="
+									type !== 'blacklist' &&
+									isSelected(featuredPlaylist._id)
+								"
 								@confirm="
-									removeIncludedPlaylist(featuredPlaylist._id)
+									deselectPlaylist(featuredPlaylist._id)
 								"
 							>
 								<i
 									class="material-icons stop-icon"
-									content="Stop playing songs from this playlist"
+									:content="`Stop ${label(
+										'present'
+									)} songs from this playlist`"
 									v-tippy
 								>
 									stop
@@ -88,39 +121,66 @@
 							</quick-confirm>
 							<i
 								v-if="
-									!isIncluded(featuredPlaylist._id) &&
-									!isBlacklisted(featuredPlaylist._id)
+									type !== 'blacklist' &&
+									!isSelected(featuredPlaylist._id) &&
+									!isSelected(
+										featuredPlaylist._id,
+										'blacklist'
+									)
 								"
-								@click="includePlaylist(featuredPlaylist)"
+								@click="selectPlaylist(featuredPlaylist)"
 								class="material-icons play-icon"
-								:content="'Play songs from this playlist'"
+								:content="`${label(
+									'future',
+									null,
+									true
+								)} songs from this playlist`"
 								v-tippy
 								>play_arrow</i
 							>
 							<quick-confirm
-								v-if="!isBlacklisted(featuredPlaylist._id)"
+								v-if="
+									type === 'blacklist' &&
+									!isSelected(
+										featuredPlaylist._id,
+										'blacklist'
+									)
+								"
 								@confirm="
-									blacklistPlaylist(featuredPlaylist._id)
+									selectPlaylist(
+										featuredPlaylist,
+										'blacklist'
+									)
 								"
 							>
 								<i
 									class="material-icons stop-icon"
-									content="Blacklist Playlist"
+									:content="`${label(
+										'future',
+										null,
+										true
+									)} Playlist`"
 									v-tippy
 									>block</i
 								>
 							</quick-confirm>
 							<quick-confirm
-								v-if="isBlacklisted(featuredPlaylist._id)"
-								@confirm="
-									removeBlacklistedPlaylist(
-										featuredPlaylist._id
+								v-if="
+									type === 'blacklist' &&
+									isSelected(
+										featuredPlaylist._id,
+										'blacklist'
 									)
 								"
+								@confirm="
+									deselectPlaylist(featuredPlaylist._id)
+								"
 							>
 								<i
 									class="material-icons stop-icon"
-									content="Stop blacklisting songs from this playlist"
+									:content="`Stop ${label(
+										'present'
+									)} songs from this playlist`"
 									v-tippy
 								>
 									stop
@@ -178,46 +238,68 @@
 					>
 						<template #item-icon>
 							<i
-								class="material-icons"
-								v-if="isIncluded(playlist._id)"
-								content="This playlist is currently included"
+								class="material-icons blacklisted-icon"
+								v-if="isSelected(playlist._id, 'blacklist')"
+								:content="`This playlist is currently ${label(
+									'past',
+									'blacklist'
+								)}`"
 								v-tippy
 							>
-								play_arrow
+								block
 							</i>
 							<i
-								class="material-icons blacklisted-icon"
-								v-else-if="isBlacklisted(playlist._id)"
-								content="This playlist is currently blacklisted"
+								class="material-icons"
+								v-else-if="isSelected(playlist._id)"
+								:content="`This playlist is currently ${label(
+									'past'
+								)}`"
 								v-tippy
 							>
-								block
+								play_arrow
 							</i>
 							<i
 								class="material-icons"
 								v-else
-								content="This playlist is currently not included or blacklisted"
+								:content="`This playlist is currently not ${label(
+									'past'
+								)}`"
 								v-tippy
 							>
-								play_disabled
+								{{
+									type === "blacklist"
+										? "block"
+										: "play_disabled"
+								}}
 							</i>
 						</template>
 
 						<template #actions>
 							<i
-								v-if="isBlacklisted(playlist._id)"
+								v-if="
+									type !== 'blacklist' &&
+									isSelected(playlist._id, 'blacklist')
+								"
 								class="material-icons stop-icon"
-								content="This playlist is blacklisted in this station"
+								:content="`This playlist is ${label(
+									'past',
+									'blacklist'
+								)} in this station`"
 								v-tippy="{ theme: 'info' }"
 								>play_disabled</i
 							>
 							<quick-confirm
-								v-if="isIncluded(playlist._id)"
-								@confirm="removeIncludedPlaylist(playlist._id)"
+								v-if="
+									type !== 'blacklist' &&
+									isSelected(playlist._id)
+								"
+								@confirm="deselectPlaylist(playlist._id)"
 							>
 								<i
 									class="material-icons stop-icon"
-									content="Stop playing songs from this playlist"
+									:content="`Stop ${label(
+										'present'
+									)} songs from this playlist`"
 									v-tippy
 								>
 									stop
@@ -225,35 +307,50 @@
 							</quick-confirm>
 							<i
 								v-if="
-									!isIncluded(playlist._id) &&
-									!isBlacklisted(playlist._id)
+									type !== 'blacklist' &&
+									!isSelected(playlist._id) &&
+									!isSelected(playlist._id, 'blacklist')
 								"
-								@click="includePlaylist(playlist)"
+								@click="selectPlaylist(playlist)"
 								class="material-icons play-icon"
-								:content="'Play songs from this playlist'"
+								:content="`${label(
+									'future',
+									null,
+									true
+								)} songs from this playlist`"
 								v-tippy
 								>play_arrow</i
 							>
 							<quick-confirm
-								v-if="!isBlacklisted(playlist._id)"
-								@confirm="blacklistPlaylist(playlist._id)"
+								v-if="
+									type === 'blacklist' &&
+									!isSelected(playlist._id, 'blacklist')
+								"
+								@confirm="selectPlaylist(playlist, 'blacklist')"
 							>
 								<i
 									class="material-icons stop-icon"
-									content="Blacklist Playlist"
+									:content="`${label(
+										'future',
+										null,
+										true
+									)} Playlist`"
 									v-tippy
 									>block</i
 								>
 							</quick-confirm>
 							<quick-confirm
-								v-if="isBlacklisted(playlist._id)"
-								@confirm="
-									removeBlacklistedPlaylist(playlist._id)
+								v-if="
+									type === 'blacklist' &&
+									isSelected(playlist._id, 'blacklist')
 								"
+								@confirm="deselectPlaylist(playlist._id)"
 							>
 								<i
 									class="material-icons stop-icon"
-									content="Stop blacklisting songs from this playlist"
+									:content="`Stop ${label(
+										'present'
+									)} songs from this playlist`"
 									v-tippy
 								>
 									stop
@@ -289,6 +386,74 @@
 					</button>
 				</div>
 			</div>
+			<div class="tab" v-show="tab === 'current'">
+				<div v-if="selectedPlaylists().length > 0">
+					<playlist-item
+						v-for="playlist in selectedPlaylists()"
+						:key="`key-${playlist._id}`"
+						:playlist="playlist"
+						:show-owner="true"
+					>
+						<template #item-icon>
+							<i
+								class="material-icons"
+								:class="{
+									'blacklisted-icon': type === 'blacklist'
+								}"
+								:content="`This playlist is currently ${label(
+									'past'
+								)}`"
+								v-tippy
+							>
+								{{
+									type === "blacklist"
+										? "block"
+										: "play_arrow"
+								}}
+							</i>
+						</template>
+
+						<template #actions>
+							<quick-confirm
+								v-if="isOwnerOrAdmin()"
+								@confirm="deselectPlaylist(playlist._id)"
+							>
+								<i
+									class="material-icons stop-icon"
+									:content="`Stop ${label(
+										'present'
+									)} songs from this playlist`"
+									v-tippy
+								>
+									stop
+								</i>
+							</quick-confirm>
+							<i
+								v-if="playlist.createdBy === myUserId"
+								@click="showPlaylist(playlist._id)"
+								class="material-icons edit-icon"
+								content="Edit Playlist"
+								v-tippy
+								>edit</i
+							>
+							<i
+								v-if="
+									playlist.createdBy !== myUserId &&
+									(playlist.privacy === 'public' || isAdmin())
+								"
+								@click="showPlaylist(playlist._id)"
+								class="material-icons edit-icon"
+								content="View Playlist"
+								v-tippy
+								>visibility</i
+							>
+						</template>
+					</playlist-item>
+				</div>
+				<p v-else class="has-text-centered scrollable-list">
+					No playlists currently included.
+				</p>
+			</div>
 			<div
 				v-if="station.type === 'community'"
 				class="tab"
@@ -324,89 +489,129 @@
 							>
 								<template #item-icon>
 									<i
-										class="material-icons"
-										v-if="isIncluded(element._id)"
-										content="This playlist is currently included"
+										class="material-icons blacklisted-icon"
+										v-if="
+											isSelected(element._id, 'blacklist')
+										"
+										:content="`This playlist is currently ${label(
+											'past',
+											'blacklist'
+										)}`"
 										v-tippy
 									>
-										play_arrow
+										block
 									</i>
 									<i
-										class="material-icons blacklisted-icon"
-										v-else-if="isBlacklisted(element._id)"
-										content="This playlist is currently blacklisted"
+										class="material-icons"
+										v-else-if="isSelected(element._id)"
+										:content="`This playlist is currently ${label(
+											'past'
+										)}`"
 										v-tippy
 									>
-										block
+										play_arrow
 									</i>
 									<i
 										class="material-icons"
 										v-else
-										content="This playlist is currently not included or blacklisted"
+										:content="`This playlist is currently not ${label(
+											'past'
+										)}`"
 										v-tippy
 									>
-										play_disabled
+										{{
+											type === "blacklist"
+												? "block"
+												: "play_disabled"
+										}}
 									</i>
 								</template>
 
 								<template #actions>
 									<i
 										v-if="
-											isOwnerOrAdmin() &&
-											!isIncluded(element._id)
+											type !== 'blacklist' &&
+											isSelected(element._id, 'blacklist')
 										"
-										@click="includePlaylist(element)"
-										class="material-icons play-icon"
-										content="Play songs from this playlist"
-										v-tippy
-										>play_arrow</i
+										class="material-icons stop-icon"
+										:content="`This playlist is ${label(
+											'past',
+											'blacklist'
+										)} in this station`"
+										v-tippy="{ theme: 'info' }"
+										>play_disabled</i
 									>
 									<quick-confirm
 										v-if="
-											isOwnerOrAdmin() &&
-											isIncluded(element._id)
-										"
-										@confirm="
-											removeIncludedPlaylist(element._id)
+											type !== 'blacklist' &&
+											isSelected(element._id)
 										"
+										@confirm="deselectPlaylist(element._id)"
 									>
 										<i
 											class="material-icons stop-icon"
-											content="Stop playing songs from this playlist"
+											:content="`Stop ${label(
+												'present'
+											)} songs from this playlist`"
 											v-tippy
-											>stop</i
 										>
+											stop
+										</i>
 									</quick-confirm>
+									<i
+										v-if="
+											type !== 'blacklist' &&
+											!isSelected(element._id) &&
+											!isSelected(
+												element._id,
+												'blacklist'
+											)
+										"
+										@click="selectPlaylist(element)"
+										class="material-icons play-icon"
+										:content="`${label(
+											'future',
+											null,
+											true
+										)} songs from this playlist`"
+										v-tippy
+										>play_arrow</i
+									>
 									<quick-confirm
 										v-if="
-											isOwnerOrAdmin() &&
-											!isBlacklisted(element._id)
+											type === 'blacklist' &&
+											!isSelected(
+												element._id,
+												'blacklist'
+											)
 										"
 										@confirm="
-											blacklistPlaylist(element._id)
+											selectPlaylist(element, 'blacklist')
 										"
 									>
 										<i
 											class="material-icons stop-icon"
-											content="Blacklist Playlist"
+											:content="`${label(
+												'future',
+												null,
+												true
+											)} Playlist`"
 											v-tippy
 											>block</i
 										>
 									</quick-confirm>
 									<quick-confirm
 										v-if="
-											isOwnerOrAdmin() &&
-											isBlacklisted(element._id)
-										"
-										@confirm="
-											removeBlacklistedPlaylist(
-												element._id
-											)
+											type === 'blacklist' &&
+											isSelected(element._id, 'blacklist')
 										"
+										@confirm="deselectPlaylist(element._id)"
 									>
 										<i
 											class="material-icons stop-icon"
-											content="Stop blacklisting songs from this playlist"
+											:content="`Stop ${label(
+												'present'
+											)} songs from this playlist`"
 											v-tippy
 										>
 											stop
@@ -429,74 +634,6 @@
 					You don't have any playlists!
 				</p>
 			</div>
-			<div class="tab" v-show="tab === 'current'">
-				<div v-if="includedPlaylists.length > 0">
-					<playlist-item
-						v-for="playlist in includedPlaylists"
-						:key="`key-${playlist._id}`"
-						:playlist="playlist"
-						:show-owner="true"
-					>
-						<template #item-icon>
-							<i
-								class="material-icons"
-								content="This playlist is currently included"
-								v-tippy
-							>
-								play_arrow
-							</i>
-						</template>
-
-						<template #actions>
-							<quick-confirm
-								v-if="isOwnerOrAdmin()"
-								@confirm="removeIncludedPlaylist(playlist._id)"
-							>
-								<i
-									class="material-icons stop-icon"
-									content="Stop playing songs from this playlist"
-									v-tippy
-								>
-									stop
-								</i>
-							</quick-confirm>
-							<quick-confirm
-								v-if="isOwnerOrAdmin()"
-								@confirm="blacklistPlaylist(playlist._id)"
-							>
-								<i
-									class="material-icons stop-icon"
-									content="Blacklist Playlist"
-									v-tippy
-									>block</i
-								>
-							</quick-confirm>
-							<i
-								v-if="playlist.createdBy === myUserId"
-								@click="showPlaylist(playlist._id)"
-								class="material-icons edit-icon"
-								content="Edit Playlist"
-								v-tippy
-								>edit</i
-							>
-							<i
-								v-if="
-									playlist.createdBy !== myUserId &&
-									(playlist.privacy === 'public' || isAdmin())
-								"
-								@click="showPlaylist(playlist._id)"
-								class="material-icons edit-icon"
-								content="View Playlist"
-								v-tippy
-								>visibility</i
-							>
-						</template>
-					</playlist-item>
-				</div>
-				<p v-else class="has-text-centered scrollable-list">
-					No playlists currently included.
-				</p>
-			</div>
 		</div>
 	</div>
 </template>
@@ -516,9 +653,20 @@ export default {
 		PlaylistItem
 	},
 	mixins: [SortablePlaylists],
+	props: {
+		type: {
+			type: String,
+			default: ""
+		},
+		sector: {
+			type: String,
+			default: "manageStation"
+		}
+	},
+	emits: ["selected"],
 	data() {
 		return {
-			tab: "included",
+			tab: "current",
 			search: {
 				query: "",
 				searchedQuery: "",
@@ -531,6 +679,36 @@ export default {
 		};
 	},
 	computed: {
+		station: {
+			get() {
+				if (this.sector === "manageStation")
+					return this.$store.state.modals.manageStation.station;
+				return this.$store.state.station.station;
+			},
+			set(station) {
+				if (this.sector === "manageStation")
+					this.$store.commit(
+						"modals/manageStation/updateStation",
+						station
+					);
+				else this.$store.commit("station/updateStation", station);
+			}
+		},
+		blacklist: {
+			get() {
+				if (this.sector === "manageStation")
+					return this.$store.state.modals.manageStation.blacklist;
+				return this.$store.state.station.blacklist;
+			},
+			set(blacklist) {
+				if (this.sector === "manageStation")
+					this.$store.commit(
+						"modals/manageStation/setBlacklist",
+						blacklist
+					);
+				else this.$store.commit("station/setBlacklist", blacklist);
+			}
+		},
 		resultsLeftCount() {
 			return this.search.count - this.search.results.length;
 		},
@@ -543,12 +721,11 @@ export default {
 			userId: state => state.user.auth.userId
 		}),
 		...mapState("modals/manageStation", {
-			parentTab: state => state.tab,
 			originalStation: state => state.originalStation,
-			station: state => state.station,
-			includedPlaylists: state => state.includedPlaylists,
-			blacklist: state => state.blacklist,
-			songsList: state => state.songsList
+			includedPlaylists: state => state.includedPlaylists
+		}),
+		...mapState("station", {
+			autoRequest: state => state.autoRequest
 		}),
 		...mapGetters({
 			socket: "websockets/getSocket"
@@ -572,17 +749,18 @@ export default {
 					this.featuredPlaylists = 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;
+			if (this.type === "autofill")
+				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.getStationBlacklistById`,
@@ -616,53 +794,126 @@ export default {
 			this.editPlaylist(playlistId);
 			this.openModal("editPlaylist");
 		},
-		includePlaylist(playlist) {
-			this.socket.dispatch(
-				"stations.includePlaylist",
-				this.station._id,
-				playlist._id,
-				res => {
-					new Toast(res.message);
-				}
-			);
+		label(tense = "future", typeOverwrite = null, capitalize = false) {
+			let label = typeOverwrite || this.type;
+
+			if (tense === "past") label = `${label}ed`;
+			if (tense === "present") label = `${label}ing`;
+
+			if (capitalize)
+				label = `${label.charAt(0).toUpperCase()}${label.slice(1)}`;
+
+			return label;
 		},
-		removeIncludedPlaylist(id) {
-			return new Promise(resolve => {
-				this.socket.dispatch(
-					"stations.removeIncludedPlaylist",
-					this.station._id,
-					id,
-					res => {
-						new Toast(res.message);
-						resolve();
-					}
+		selectedPlaylists(typeOverwrite) {
+			const type = typeOverwrite || this.type;
+
+			if (type === "autofill") return this.includedPlaylists;
+			if (type === "blacklist") return this.blacklist;
+			if (type === "autorequest") return this.autoRequest;
+			return [];
+		},
+		async selectPlaylist(playlist, typeOverwrite) {
+			const type = typeOverwrite || this.type;
+
+			if (this.isSelected(playlist._id, type))
+				return new Toast(
+					`Error: Playlist already ${this.label("past", type)}.`
 				);
-			});
+
+			if (type === "autofill")
+				return new Promise(resolve => {
+					this.socket.dispatch(
+						"stations.includePlaylist",
+						this.station._id,
+						playlist._id,
+						res => {
+							new Toast(res.message);
+							this.$emit("selected");
+							resolve();
+						}
+					);
+				});
+			if (type === "blacklist") {
+				if (this.type !== "blacklist" && this.isSelected(playlist._id))
+					await this.deselectPlaylist(playlist._id);
+
+				return new Promise(resolve => {
+					this.socket.dispatch(
+						"stations.blacklistPlaylist",
+						this.station._id,
+						playlist._id,
+						res => {
+							new Toast(res.message);
+							this.$emit("selected");
+							resolve();
+						}
+					);
+				});
+			}
+			if (type === "autorequest")
+				return new Promise(resolve => {
+					this.autoRequest.push(playlist);
+					new Toast(
+						"Successfully selected playlist to auto request songs."
+					);
+					this.$emit("selected");
+					resolve();
+				});
+			return false;
 		},
-		removeBlacklistedPlaylist(id) {
-			return new Promise(resolve => {
-				this.socket.dispatch(
-					"stations.removeBlacklistedPlaylist",
-					this.station._id,
-					id,
-					res => {
-						new Toast(res.message);
+		deselectPlaylist(playlistId, typeOverwrite) {
+			const type = typeOverwrite || this.type;
+
+			if (type === "autofill")
+				return new Promise(resolve => {
+					this.socket.dispatch(
+						"stations.removeIncludedPlaylist",
+						this.station._id,
+						playlistId,
+						res => {
+							new Toast(res.message);
+							resolve();
+						}
+					);
+				});
+			if (type === "blacklist")
+				return new Promise(resolve => {
+					this.socket.dispatch(
+						"stations.removeBlacklistedPlaylist",
+						this.station._id,
+						playlistId,
+						res => {
+							new Toast(res.message);
+							resolve();
+						}
+					);
+				});
+			if (type === "autorequest")
+				return new Promise(resolve => {
+					let selected = false;
+					this.autoRequest.forEach((playlist, index) => {
+						if (playlist._id === playlistId) {
+							selected = true;
+							this.autoRequest.splice(index, 1);
+						}
+					});
+					if (selected) {
+						new Toast("Successfully deselected playlist.");
+						resolve();
+					} else {
+						new Toast("Playlist not selected.");
 						resolve();
 					}
-				);
-			});
-		},
-		isIncluded(id) {
-			let included = false;
-			this.includedPlaylists.forEach(playlist => {
-				if (playlist._id === id) included = true;
-			});
-			return included;
+				});
+			return false;
 		},
-		isBlacklisted(id) {
+		isSelected(playlistId, typeOverwrite) {
+			const type = typeOverwrite || this.type;
 			let selected = false;
-			this.blacklist.forEach(playlist => {
-				if (playlist._id === id) selected = true;
+
+			this.selectedPlaylists(type).forEach(playlist => {
+				if (playlist._id === playlistId) selected = true;
 			});
 			return selected;
 		},
@@ -708,18 +959,6 @@ export default {
 				}
 			});
 		},
-		async blacklistPlaylist(id) {
-			if (this.isIncluded(id)) await this.removeIncludedPlaylist(id);
-
-			this.socket.dispatch(
-				"stations.blacklistPlaylist",
-				this.station._id,
-				id,
-				res => {
-					new Toast(res.message);
-				}
-			);
-		},
 		...mapActions("modalVisibility", ["openModal"]),
 		...mapActions("user/playlists", ["editPlaylist", "setPlaylists"])
 	}
@@ -746,7 +985,12 @@ export default {
 	color: var(--purple);
 }
 
-.station-playlists {
+.playlist-tab-base {
+	.top-info {
+		font-size: 15px;
+		margin-bottom: 15px;
+	}
+
 	.tabs-container {
 		.tab-selection {
 			display: flex;

+ 29 - 725
frontend/src/components/Request.vue

@@ -85,12 +85,11 @@
 							</template>
 						</song-item>
 						<button
-							v-if="playlistResultsLeftCount > 0"
+							v-if="musareResultsLeftCount > 0"
 							class="button is-primary load-more-button"
 							@click="searchForMusareSongs(musareSearch.page + 1)"
 						>
-							Load {{ nextPagePlaylistsResultsCount }} more
-							results
+							Load {{ nextPageMusareResultsCount }} more results
 						</button>
 					</div>
 				</div>
@@ -167,514 +166,14 @@
 					</div>
 				</div>
 			</div>
-			<div v-if="sector === 'station'" v-show="tab === 'autorequest'">
-				<div class="tab-selection">
-					<button
-						class="button is-default"
-						ref="search-child-tab"
-						:class="{ selected: childTab === 'search' }"
-						@click="showChildTab('search')"
-					>
-						Search
-					</button>
-					<button
-						class="button is-default"
-						ref="current-child-tab"
-						:class="{ selected: childTab === 'current' }"
-						@click="showChildTab('current')"
-					>
-						Current
-					</button>
-					<button
-						class="button is-default"
-						ref="my-playlists-child-tab"
-						:class="{ selected: childTab === 'my-playlists' }"
-						@click="showChildTab('my-playlists')"
-					>
-						My Playlists
-					</button>
-				</div>
-				<div class="tab" v-show="childTab === 'search'">
-					<div v-if="featuredPlaylists.length > 0">
-						<label class="label"> Featured playlists </label>
-						<playlist-item
-							v-for="featuredPlaylist in featuredPlaylists"
-							:key="`featuredKey-${featuredPlaylist._id}`"
-							:playlist="featuredPlaylist"
-							:show-owner="true"
-						>
-							<template #item-icon>
-								<i
-									class="material-icons"
-									v-if="isSelected(featuredPlaylist._id)"
-									content="This playlist is currently selected"
-									v-tippy
-								>
-									radio
-								</i>
-								<i
-									class="material-icons blacklisted-icon"
-									v-else-if="
-										isBlacklisted(featuredPlaylist._id)
-									"
-									content="This playlist is currently blacklisted"
-									v-tippy
-								>
-									block
-								</i>
-								<i
-									class="material-icons"
-									v-else
-									content="This playlist is currently not selected or blacklisted"
-									v-tippy
-								>
-									play_disabled
-								</i>
-							</template>
-
-							<template #actions>
-								<i
-									v-if="isBlacklisted(featuredPlaylist._id)"
-									class="material-icons stop-icon"
-									content="This playlist is blacklisted in this station"
-									v-tippy="{ theme: 'info' }"
-									>play_disabled</i
-								>
-								<quick-confirm
-									v-if="isSelected(featuredPlaylist._id)"
-									@confirm="deselect(featuredPlaylist._id)"
-								>
-									<i
-										class="material-icons stop-icon"
-										content="Stop playing songs from this playlist"
-										v-tippy
-									>
-										stop
-									</i>
-								</quick-confirm>
-								<i
-									v-if="
-										!isSelected(featuredPlaylist._id) &&
-										!isBlacklisted(featuredPlaylist._id)
-									"
-									@click="select(featuredPlaylist)"
-									class="material-icons play-icon"
-									content="Request songs from this playlist"
-									v-tippy
-									>play_arrow</i
-								>
-								<quick-confirm
-									v-if="
-										isOwnerOrAdmin() &&
-										!isBlacklisted(featuredPlaylist._id)
-									"
-									@confirm="
-										blacklistPlaylist(featuredPlaylist._id)
-									"
-								>
-									<i
-										class="material-icons stop-icon"
-										content="Blacklist Playlist"
-										v-tippy
-										>block</i
-									>
-								</quick-confirm>
-								<quick-confirm
-									v-if="
-										isOwnerOrAdmin() &&
-										isBlacklisted(featuredPlaylist._id)
-									"
-									@confirm="
-										removeBlacklistedPlaylist(
-											featuredPlaylist._id
-										)
-									"
-								>
-									<i
-										class="material-icons stop-icon"
-										content="Stop blacklisting songs from this playlist"
-										v-tippy
-									>
-										stop
-									</i>
-								</quick-confirm>
-								<i
-									v-if="
-										featuredPlaylist.createdBy === myUserId
-									"
-									@click="showPlaylist(featuredPlaylist._id)"
-									class="material-icons edit-icon"
-									content="Edit Playlist"
-									v-tippy
-									>edit</i
-								>
-								<i
-									v-if="
-										featuredPlaylist.createdBy !==
-											myUserId &&
-										(featuredPlaylist.privacy ===
-											'public' ||
-											isAdmin())
-									"
-									@click="showPlaylist(featuredPlaylist._id)"
-									class="material-icons edit-icon"
-									content="View Playlist"
-									v-tippy
-									>visibility</i
-								>
-							</template>
-						</playlist-item>
-						<br />
-					</div>
-					<label class="label"> Search for a public playlist </label>
-					<div class="control is-grouped input-with-button">
-						<p class="control is-expanded">
-							<input
-								class="input"
-								type="text"
-								placeholder="Enter your playlist query here..."
-								v-model="playlistSearch.query"
-								@keyup.enter="searchForPlaylists(1)"
-							/>
-						</p>
-						<p class="control">
-							<a
-								class="button is-info"
-								@click="searchForPlaylists(1)"
-								><i class="material-icons icon-with-button"
-									>search</i
-								>Search</a
-							>
-						</p>
-					</div>
-					<div v-if="playlistSearch.results.length > 0">
-						<playlist-item
-							v-for="playlist in playlistSearch.results"
-							:key="`searchKey-${playlist._id}`"
-							:playlist="playlist"
-							:show-owner="true"
-						>
-							<template #item-icon>
-								<i
-									class="material-icons"
-									v-if="isSelected(playlist._id)"
-									content="This playlist is currently selected"
-									v-tippy
-								>
-									radio
-								</i>
-								<i
-									class="material-icons blacklisted-icon"
-									v-else-if="isBlacklisted(playlist._id)"
-									content="This playlist is currently blacklisted"
-									v-tippy
-								>
-									block
-								</i>
-								<i
-									class="material-icons"
-									v-else
-									content="This playlist is currently not selected or blacklisted"
-									v-tippy
-								>
-									play_disabled
-								</i>
-							</template>
-
-							<template #actions>
-								<i
-									v-if="isBlacklisted(playlist._id)"
-									class="material-icons stop-icon"
-									content="This playlist is blacklisted in this station"
-									v-tippy="{ theme: 'info' }"
-									>play_disabled</i
-								>
-								<quick-confirm
-									v-if="isSelected(playlist._id)"
-									@confirm="deselect(playlist._id)"
-								>
-									<i
-										class="material-icons stop-icon"
-										content="Stop playing songs from this playlist"
-										v-tippy
-									>
-										stop
-									</i>
-								</quick-confirm>
-								<i
-									v-if="
-										!isSelected(playlist._id) &&
-										!isBlacklisted(playlist._id)
-									"
-									@click="select(playlist)"
-									class="material-icons play-icon"
-									content="Request songs from this playlist"
-									v-tippy
-									>play_arrow</i
-								>
-								<quick-confirm
-									v-if="
-										isOwnerOrAdmin() &&
-										!isBlacklisted(playlist._id)
-									"
-									@confirm="blacklistPlaylist(playlist._id)"
-								>
-									<i
-										class="material-icons stop-icon"
-										content="Blacklist Playlist"
-										v-tippy
-										>block</i
-									>
-								</quick-confirm>
-								<quick-confirm
-									v-if="
-										isOwnerOrAdmin() &&
-										isBlacklisted(playlist._id)
-									"
-									@confirm="
-										removeBlacklistedPlaylist(playlist._id)
-									"
-								>
-									<i
-										class="material-icons stop-icon"
-										content="Stop blacklisting songs from this playlist"
-										v-tippy
-									>
-										stop
-									</i>
-								</quick-confirm>
-								<i
-									v-if="playlist.createdBy === myUserId"
-									@click="showPlaylist(playlist._id)"
-									class="material-icons edit-icon"
-									content="Edit Playlist"
-									v-tippy
-									>edit</i
-								>
-								<i
-									v-if="
-										playlist.createdBy !== myUserId &&
-										(playlist.privacy === 'public' ||
-											isAdmin())
-									"
-									@click="showPlaylist(playlist._id)"
-									class="material-icons edit-icon"
-									content="View Playlist"
-									v-tippy
-									>visibility</i
-								>
-							</template>
-						</playlist-item>
-						<button
-							v-if="playlistResultsLeftCount > 0"
-							class="button is-primary load-more-button"
-							@click="searchForPlaylists(playlistSearch.page + 1)"
-						>
-							Load {{ nextPagePlaylistsResultsCount }} more
-							results
-						</button>
-					</div>
-				</div>
-				<div class="tab" v-show="childTab === 'my-playlists'">
-					<button
-						class="button is-primary"
-						id="create-new-playlist-button"
-						@click="openModal('createPlaylist')"
-					>
-						Create new playlist
-					</button>
-					<div
-						class="menu-list scrollable-list"
-						v-if="playlists.length > 0"
-					>
-						<draggable
-							tag="transition-group"
-							:component-data="{
-								name: !drag ? 'draggable-list-transition' : null
-							}"
-							item-key="_id"
-							v-model="playlists"
-							v-bind="dragOptions"
-							@start="drag = true"
-							@end="drag = false"
-							@change="savePlaylistOrder"
-						>
-							<template #item="{ element }">
-								<playlist-item
-									class="item-draggable"
-									:playlist="element"
-								>
-									<template #item-icon>
-										<i
-											class="material-icons"
-											v-if="isSelected(element._id)"
-											content="This playlist is currently selected"
-											v-tippy
-										>
-											radio
-										</i>
-										<i
-											class="material-icons blacklisted-icon"
-											v-else-if="
-												isBlacklisted(element._id)
-											"
-											content="This playlist is currently blacklisted"
-											v-tippy
-										>
-											block
-										</i>
-										<i
-											class="material-icons"
-											v-else
-											content="This playlist is currently not selected or blacklisted"
-											v-tippy
-										>
-											play_disabled
-										</i>
-									</template>
-
-									<template #actions>
-										<i
-											v-if="!isSelected(element._id)"
-											@click="select(element)"
-											class="material-icons play-icon"
-											content="Request songs from this playlist"
-											v-tippy
-											>play_arrow</i
-										>
-										<quick-confirm
-											v-if="isSelected(element._id)"
-											@confirm="deselect(element._id)"
-										>
-											<i
-												class="material-icons stop-icon"
-												content="Stop requesting songs from this playlist"
-												v-tippy
-												>stop</i
-											>
-										</quick-confirm>
-										<quick-confirm
-											v-if="
-												isOwnerOrAdmin() &&
-												!isBlacklisted(element._id)
-											"
-											@confirm="
-												blacklistPlaylist(element._id)
-											"
-										>
-											<i
-												class="material-icons stop-icon"
-												content="Blacklist Playlist"
-												v-tippy
-												>block</i
-											>
-										</quick-confirm>
-										<quick-confirm
-											v-if="
-												isOwnerOrAdmin() &&
-												isBlacklisted(element._id)
-											"
-											@confirm="
-												removeBlacklistedPlaylist(
-													element._id
-												)
-											"
-										>
-											<i
-												class="material-icons stop-icon"
-												content="Stop blacklisting songs from this playlist"
-												v-tippy
-											>
-												stop
-											</i>
-										</quick-confirm>
-										<i
-											@click="showPlaylist(element._id)"
-											class="material-icons edit-icon"
-											content="Edit Playlist"
-											v-tippy
-											>edit</i
-										>
-									</template>
-								</playlist-item>
-							</template>
-						</draggable>
-					</div>
-
-					<p v-else class="has-text-centered scrollable-list">
-						You don't have any playlists!
-					</p>
-				</div>
-				<div class="tab" v-show="childTab === 'current'">
-					<div v-if="autoRequest.length > 0">
-						<playlist-item
-							v-for="playlist in autoRequest"
-							:key="`key-${playlist._id}`"
-							:playlist="playlist"
-							:show-owner="true"
-						>
-							<template #item-icon>
-								<i
-									class="material-icons"
-									content="This playlist is currently selected"
-									v-tippy
-								>
-									radio
-								</i>
-							</template>
-
-							<template #actions>
-								<quick-confirm
-									v-if="isOwnerOrAdmin()"
-									@confirm="deselect(playlist._id)"
-								>
-									<i
-										class="material-icons stop-icon"
-										content="Stop playing songs from this playlist"
-										v-tippy
-									>
-										stop
-									</i>
-								</quick-confirm>
-								<quick-confirm
-									v-if="isOwnerOrAdmin()"
-									@confirm="blacklistPlaylist(playlist._id)"
-								>
-									<i
-										class="material-icons stop-icon"
-										content="Blacklist Playlist"
-										v-tippy
-										>block</i
-									>
-								</quick-confirm>
-								<i
-									v-if="playlist.createdBy === myUserId"
-									@click="showPlaylist(playlist._id)"
-									class="material-icons edit-icon"
-									content="Edit Playlist"
-									v-tippy
-									>edit</i
-								>
-								<i
-									v-if="
-										playlist.createdBy !== myUserId &&
-										(playlist.privacy === 'public' ||
-											isAdmin())
-									"
-									@click="showPlaylist(playlist._id)"
-									class="material-icons edit-icon"
-									content="View Playlist"
-									v-tippy
-									>visibility</i
-								>
-							</template>
-						</playlist-item>
-					</div>
-					<p v-else class="has-text-centered scrollable-list">
-						No playlists currently being played.
-					</p>
-				</div>
-			</div>
+			<playlist-tab-base
+				v-if="sector === 'station'"
+				class="tab"
+				v-show="tab === 'autorequest'"
+				:type="'autorequest'"
+				:sector="sector"
+				@selected="autoRequestSong()"
+			/>
 		</div>
 	</div>
 </template>
@@ -682,53 +181,37 @@
 import { mapActions, mapState, mapGetters } from "vuex";
 
 import Toast from "toasters";
-import ws from "@/ws";
 
-import QuickConfirm from "@/components/QuickConfirm.vue";
 import SongItem from "@/components/SongItem.vue";
-import PlaylistItem from "@/components/PlaylistItem.vue";
 import SearchQueryItem from "@/components/SearchQueryItem.vue";
+import PlaylistTabBase from "@/components/PlaylistTabBase.vue";
 
-import SortablePlaylists from "@/mixins/SortablePlaylists.vue";
 import SearchYoutube from "@/mixins/SearchYoutube.vue";
 import SearchMusare from "@/mixins/SearchMusare.vue";
 
 export default {
 	components: {
-		QuickConfirm,
 		SongItem,
-		PlaylistItem,
-		SearchQueryItem
+		SearchQueryItem,
+		PlaylistTabBase
 	},
-	mixins: [SortablePlaylists, SearchYoutube, SearchMusare],
+	mixins: [SearchYoutube, SearchMusare],
 	props: {
 		sector: { type: String, default: "station" }
 	},
 	data() {
 		return {
-			tab: "songs",
-			childTab: "search",
-			playlistSearch: {
-				query: "",
-				searchedQuery: "",
-				page: 0,
-				count: 0,
-				resultsLeft: 0,
-				results: []
-			},
-			featuredPlaylists: []
+			tab: "songs"
 		};
 	},
 	computed: {
-		playlistResultsLeftCount() {
-			return (
-				this.playlistSearch.count - this.playlistSearch.results.length
-			);
+		musareResultsLeftCount() {
+			return this.musareSearch.count - this.musareSearch.results.length;
 		},
-		nextPagePlaylistSearchResultsCount() {
+		nextPageMusareResultsCount() {
 			return Math.min(
-				this.playlistSearch.pageSize,
-				this.playlistResultsLeftCount
+				this.musareSearch.pageSize,
+				this.musareResultsLeftCount
 			);
 		},
 		songsInQueue() {
@@ -762,170 +245,15 @@ export default {
 	mounted() {
 		this.showTab("songs");
 
-		ws.onConnect(this.init);
-
 		this.socket.on("event:station.queue.updated", () =>
 			this.autoRequestSong()
 		);
 	},
 	methods: {
-		init() {
-			this.socket.dispatch("playlists.indexMyPlaylists", res => {
-				if (res.status === "success")
-					this.setPlaylists(res.data.playlists);
-				this.orderOfPlaylists = this.calculatePlaylistOrder(); // order in regards to the database
-			});
-
-			this.socket.dispatch("playlists.indexFeaturedPlaylists", res => {
-				if (res.status === "success")
-					this.featuredPlaylists = res.data.playlists;
-			});
-
-			this.socket.dispatch(
-				`stations.getStationBlacklistById`,
-				this.station._id,
-				res => {
-					if (res.status === "success") {
-						this.station.blacklist = res.data.playlists;
-					}
-				}
-			);
-		},
 		showTab(tab) {
 			this.$refs[`${tab}-tab`].scrollIntoView({ block: "nearest" });
 			this.tab = tab;
 		},
-		showChildTab(tab) {
-			this.$refs[`${tab}-child-tab`].scrollIntoView({ block: "nearest" });
-			this.childTab = tab;
-		},
-		isOwner() {
-			return (
-				this.loggedIn &&
-				this.station &&
-				this.userId === this.station.owner
-			);
-		},
-		isAdmin() {
-			return this.loggedIn && this.role === "admin";
-		},
-		isOwnerOrAdmin() {
-			return this.isOwner() || this.isAdmin();
-		},
-		showPlaylist(playlistId) {
-			this.editPlaylist(playlistId);
-			this.openModal("editPlaylist");
-		},
-		select(playlist) {
-			if (!this.isSelected(playlist.id)) {
-				this.autoRequest.push(playlist);
-				this.autoRequestSong();
-				new Toast(
-					"Successfully selected playlist to auto request songs."
-				);
-			} else {
-				new Toast("Error: Playlist already selected.");
-			}
-		},
-		deselect(id) {
-			return new Promise(resolve => {
-				let selected = false;
-				this.autoRequest.forEach((playlist, index) => {
-					if (playlist._id === id) {
-						selected = true;
-						this.autoRequest.splice(index, 1);
-					}
-				});
-				if (selected) {
-					new Toast("Successfully deselected playlist.");
-					resolve();
-				} else {
-					new Toast("Playlist not selected.");
-					resolve();
-				}
-			});
-		},
-		removeBlacklistedPlaylist(id) {
-			return new Promise(resolve => {
-				this.socket.dispatch(
-					"stations.removeBlacklistedPlaylist",
-					this.station._id,
-					id,
-					res => {
-						new Toast(res.message);
-						resolve();
-					}
-				);
-			});
-		},
-		isSelected(id) {
-			let selected = false;
-			this.autoRequest.forEach(playlist => {
-				if (playlist._id === id) selected = true;
-			});
-			return selected;
-		},
-		isBlacklisted(id) {
-			let selected = false;
-			this.blacklist.forEach(playlist => {
-				if (playlist._id === id) selected = true;
-			});
-			return selected;
-		},
-		searchForPlaylists(page) {
-			if (
-				this.playlistSearch.page >= page ||
-				this.playlistSearch.searchedQuery !== this.playlistSearch.query
-			) {
-				this.playlistSearch.results = [];
-				this.playlistSearch.page = 0;
-				this.playlistSearch.count = 0;
-				this.playlistSearch.resultsLeft = 0;
-				this.playlistSearch.pageSize = 0;
-			}
-
-			const { query } = this.playlistSearch;
-			const action =
-				this.station.type === "official"
-					? "playlists.searchOfficial"
-					: "playlists.searchCommunity";
-
-			this.playlistSearch.searchedQuery = this.playlistSearch.query;
-			this.socket.dispatch(action, query, page, res => {
-				const { data } = res;
-				if (res.status === "success") {
-					const { count, pageSize, playlists } = data;
-					this.playlistSearch.results = [
-						...this.playlistSearch.results,
-						...playlists
-					];
-					this.playlistSearch.page = page;
-					this.playlistSearch.count = count;
-					this.playlistSearch.resultsLeft =
-						count - this.playlistSearch.results.length;
-					this.playlistSearch.pageSize = pageSize;
-				} else if (res.status === "error") {
-					this.playlistSearch.results = [];
-					this.playlistSearch.page = 0;
-					this.playlistSearch.count = 0;
-					this.playlistSearch.resultsLeft = 0;
-					this.playlistSearch.pageSize = 0;
-					new Toast(res.message);
-				}
-			});
-		},
-		async blacklistPlaylist(id) {
-			// if (this.isIncluded(id)) await this.removeIncludedPlaylist(id);
-
-			this.socket.dispatch(
-				"stations.blacklistPlaylist",
-				this.station._id,
-				id,
-				res => {
-					new Toast(res.message);
-				}
-			);
-		},
 		autoRequestSong() {
 			if (
 				!this.autoRequestLock &&
@@ -980,13 +308,7 @@ export default {
 				}
 			);
 		},
-		...mapActions("station", ["updateAutoRequest"]),
-		...mapActions("station", [
-			"updateAutoRequest",
-			"updateAutoRequestLock"
-		]),
-		...mapActions("modalVisibility", ["openModal"]),
-		...mapActions("user/playlists", ["editPlaylist", "setPlaylists"])
+		...mapActions("station", ["updateAutoRequest", "updateAutoRequestLock"])
 	}
 };
 </script>
@@ -999,19 +321,7 @@ export default {
 	}
 }
 
-.blacklisted-icon {
-	color: var(--dark-red);
-}
-
-.included-icon {
-	color: var(--green);
-}
-
-.selected-icon {
-	color: var(--purple);
-}
-
-#create-new-playlist-button {
+:deep(#create-new-playlist-button) {
 	width: 100%;
 }
 
@@ -1042,15 +352,9 @@ export default {
 				font-weight: 600;
 			}
 		}
-		& > div > .tab-selection .button {
-			margin-top: 5px;
-			font-size: 12px;
-			height: 28px;
-		}
 		.tab {
-			padding: 15px 0;
+			padding: 10px 0;
 			border-radius: 0;
-			.playlist-item:not(:last-of-type),
 			.item.item-draggable:not(:last-of-type) {
 				margin-bottom: 10px;
 			}
@@ -1061,12 +365,12 @@ export default {
 		}
 	}
 }
-.draggable-list-transition-move {
-	transition: transform 0.5s;
-}
 
-.draggable-list-ghost {
-	opacity: 0.5;
-	filter: brightness(95%);
+.youtube-search {
+	margin-top: 10px;
+
+	.search-query-item:not(:last-of-type) {
+		margin-bottom: 10px;
+	}
 }
 </style>

+ 0 - 0
frontend/src/components/modals/ManageStation/Tabs/Settings.vue → frontend/src/components/modals/ManageStation/Settings.vue


+ 0 - 213
frontend/src/components/modals/ManageStation/Tabs/Blacklist.vue

@@ -1,213 +0,0 @@
-<template>
-	<div class="station-blacklist">
-		<div class="tabs-container">
-			<div class="tab" v-if="isOwnerOrAdmin()">
-				<div v-if="blacklist.length > 0">
-					<playlist-item
-						:playlist="playlist"
-						v-for="playlist in blacklist"
-						:key="`key-${playlist._id}`"
-					>
-						<template #item-icon>
-							<i
-								class="material-icons blacklisted-icon"
-								content="This playlist is currently blacklisted"
-								v-tippy
-							>
-								block
-							</i>
-						</template>
-
-						<template #actions>
-							<quick-confirm
-								@confirm="
-									removeBlacklistedPlaylist(playlist._id)
-								"
-							>
-								<i
-									class="material-icons stop-icon"
-									content="Stop blacklisting songs from this playlist
-							"
-									v-tippy
-									>stop</i
-								>
-							</quick-confirm>
-							<i
-								v-if="playlist.createdBy === userId"
-								@click="showPlaylist(playlist._id)"
-								class="material-icons edit-icon"
-								content="Edit Playlist"
-								v-tippy
-								>edit</i
-							>
-							<i
-								v-else
-								@click="showPlaylist(playlist._id)"
-								class="material-icons edit-icon"
-								content="View Playlist"
-								v-tippy
-								>visibility</i
-							>
-						</template>
-					</playlist-item>
-				</div>
-				<p v-else class="has-text-centered scrollable-list">
-					No playlists currently blacklisted.
-				</p>
-			</div>
-		</div>
-	</div>
-</template>
-<script>
-import { mapActions, mapState, mapGetters } from "vuex";
-import Toast from "toasters";
-import ws from "@/ws";
-
-import QuickConfirm from "@/components/QuickConfirm.vue";
-import PlaylistItem from "@/components/PlaylistItem.vue";
-
-export default {
-	components: {
-		QuickConfirm,
-		PlaylistItem
-	},
-	data() {
-		return {};
-	},
-	computed: {
-		...mapState({
-			loggedIn: state => state.user.auth.loggedIn,
-			role: state => state.user.auth.role,
-			userId: state => state.user.auth.userId
-		}),
-		...mapState("modals/manageStation", {
-			originalStation: state => state.originalStation,
-			station: state => state.station,
-			includedPlaylists: state => state.includedPlaylists,
-			blacklist: state => state.blacklist
-		}),
-		...mapGetters({
-			socket: "websockets/getSocket"
-		})
-	},
-	mounted() {
-		ws.onConnect(this.init);
-	},
-	methods: {
-		init() {
-			this.socket.dispatch(
-				`stations.getStationBlacklistById`,
-				this.station._id,
-				res => {
-					if (res.status === "success") {
-						this.station.blacklist = res.data.playlists;
-						this.originalStation.blacklist = res.data.playlists;
-					}
-				}
-			);
-		},
-		isOwner() {
-			return (
-				this.loggedIn &&
-				this.station &&
-				this.userId === this.station.owner
-			);
-		},
-		isAdmin() {
-			return this.loggedIn && this.role === "admin";
-		},
-		isOwnerOrAdmin() {
-			return this.isOwner() || this.isAdmin();
-		},
-		showPlaylist(playlistId) {
-			this.editPlaylist(playlistId);
-			this.openModal("editPlaylist");
-		},
-		removeIncludedPlaylist(id) {
-			return new Promise(resolve => {
-				this.socket.dispatch(
-					"stations.removeIncludedPlaylist",
-					this.station._id,
-					id,
-					res => {
-						new Toast(res.message);
-						resolve();
-					}
-				);
-			});
-		},
-		removeBlacklistedPlaylist(id) {
-			return new Promise(resolve => {
-				this.socket.dispatch(
-					"stations.removeBlacklistedPlaylist",
-					this.station._id,
-					id,
-					res => {
-						new Toast(res.message);
-						resolve();
-					}
-				);
-			});
-		},
-		isIncluded(id) {
-			let included = false;
-			this.includedPlaylists.forEach(playlist => {
-				if (playlist._id === id) included = true;
-			});
-			return included;
-		},
-		isBlacklisted(id) {
-			let selected = false;
-			this.blacklist.forEach(playlist => {
-				if (playlist._id === id) selected = true;
-			});
-			return selected;
-		},
-		async blacklistPlaylist(id) {
-			if (this.isIncluded(id)) await this.removeIncludedPlaylist(id);
-
-			this.socket.dispatch(
-				"stations.blacklistPlaylist",
-				this.station._id,
-				id,
-				res => {
-					new Toast(res.message);
-				}
-			);
-		},
-		...mapActions("modalVisibility", ["openModal"]),
-		...mapActions("user/playlists", ["editPlaylist"])
-	}
-};
-</script>
-
-<style lang="less" scoped>
-.blacklisted-icon {
-	color: var(--dark-red);
-}
-
-.included-icon {
-	color: var(--green);
-}
-
-.selected-icon {
-	color: var(--purple);
-}
-
-.station-blacklist {
-	.tabs-container {
-		.tab {
-			padding: 15px 0;
-			border-radius: 0;
-			.playlist-item:not(:last-of-type),
-			.item.item-draggable:not(:last-of-type) {
-				margin-bottom: 10px;
-			}
-			.load-more-button {
-				width: 100%;
-				margin-top: 10px;
-			}
-		}
-	}
-}
-</style>

+ 16 - 9
frontend/src/components/modals/ManageStation/index.vue

@@ -123,10 +123,11 @@
 							class="tab"
 							v-show="tab === 'settings'"
 						/>
-						<autofill
+						<playlist-tab-base
 							v-if="isOwnerOrAdmin()"
 							class="tab"
 							v-show="tab === 'autofill'"
+							:type="'autofill'"
 						/>
 						<request
 							v-if="canRequest()"
@@ -134,11 +135,19 @@
 							v-show="tab === 'request'"
 							:sector="sector"
 						/>
-						<blacklist
+						<playlist-tab-base
 							v-if="isOwnerOrAdmin()"
 							class="tab"
 							v-show="tab === 'blacklist'"
-						/>
+							:type="'blacklist'"
+						>
+							<template #info>
+								<p>
+									Blacklist a playlist to prevent all songs
+									within from playing in this station.
+								</p>
+							</template>
+						</playlist-tab-base>
 					</div>
 				</div>
 			</div>
@@ -184,10 +193,9 @@ import Queue from "@/components/Queue.vue";
 import SongItem from "@/components/SongItem.vue";
 import Modal from "../../Modal.vue";
 
-import Settings from "./Tabs/Settings.vue";
-import Autofill from "./Tabs/Autofill.vue";
+import Settings from "./Settings.vue";
+import PlaylistTabBase from "@/components/PlaylistTabBase.vue";
 import Request from "@/components/Request.vue";
-import Blacklist from "./Tabs/Blacklist.vue";
 
 export default {
 	components: {
@@ -196,9 +204,8 @@ export default {
 		Queue,
 		SongItem,
 		Settings,
-		Autofill,
-		Request,
-		Blacklist
+		PlaylistTabBase,
+		Request
 	},
 	props: {
 		stationId: { type: String, default: "" },

+ 5 - 1
frontend/src/pages/Station/Sidebar/index.vue

@@ -174,8 +174,12 @@ export default {
 		margin-bottom: 20px;
 		border-radius: 0 0 @border-radius @border-radius;
 		max-height: 100%;
-		padding: 15px;
+		padding: 10px;
 		overflow-y: auto;
+
+		.scrollable-list {
+			padding: 10px 0;
+		}
 	}
 }