Sfoglia il codice sorgente

refactor: Initial station mode refactoring (WIP)

Owen Diffey 3 anni fa
parent
commit
605d68744c

+ 3 - 7
backend/logic/actions/stations.js

@@ -2902,7 +2902,6 @@ export default {
 
 
 				(station, next) => {
 				(station, next) => {
 					if (!station) return next("Station not found.");
 					if (!station) return next("Station not found.");
-					if (!station.partyMode) return next("Station is not in party mode.");
 
 
 					if (station.locked) {
 					if (station.locked) {
 						return userModel.findOne({ _id: session.userId }, (err, user) => {
 						return userModel.findOne({ _id: session.userId }, (err, user) => {
@@ -2915,10 +2914,8 @@ export default {
 					return next(null, station);
 					return next(null, station);
 				},
 				},
 
 
-				(station, next) => {
-					if (station.type !== "community") return next("That station is not a community station.");
-
-					return StationsModule.runJob(
+				(station, next) =>
+					StationsModule.runJob(
 						"CAN_USER_VIEW_STATION",
 						"CAN_USER_VIEW_STATION",
 						{
 						{
 							station,
 							station,
@@ -2930,8 +2927,7 @@ export default {
 							if (canView) return next(null, station);
 							if (canView) return next(null, station);
 							return next("Insufficient permissions.");
 							return next("Insufficient permissions.");
 						})
 						})
-						.catch(err => next(err));
-				},
+						.catch(err => next(err)),
 
 
 				(station, next) => {
 				(station, next) => {
 					if (station.currentSong && station.currentSong.youtubeId === youtubeId)
 					if (station.currentSong && station.currentSong.youtubeId === youtubeId)

+ 24 - 73
backend/logic/stations.js

@@ -556,6 +556,7 @@ class _StationsModule extends CoreClass {
 					(currentSongs, songsToAdd, currentSongIndex, next) => {
 					(currentSongs, songsToAdd, currentSongIndex, next) => {
 						const newPlaylist = [...currentSongs, ...songsToAdd].map(song => {
 						const newPlaylist = [...currentSongs, ...songsToAdd].map(song => {
 							if (!song._id) song._id = null;
 							if (!song._id) song._id = null;
+							song.requestedAt = Date.now();
 							return song;
 							return song;
 						});
 						});
 						next(null, newPlaylist, currentSongIndex);
 						next(null, newPlaylist, currentSongIndex);
@@ -750,80 +751,30 @@ class _StationsModule extends CoreClass {
 					(station, next) => {
 					(station, next) => {
 						if (!station) return next("Station not found.");
 						if (!station) return next("Station not found.");
 
 
-						if (station.type === "community" && station.partyMode && station.queue.length === 0)
-							return next(null, null, station); // Community station with party mode enabled and no songs in the queue
-
-						if (station.type === "community" && station.partyMode && station.queue.length > 0) {
-							// Community station with party mode enabled and songs in the queue
-							if (station.paused) return next(null, null, station);
-
-							StationsModule.runJob("GET_NEXT_STATION_SONG", { stationId: station._id }, this)
-								.then(response => {
-									StationsModule.runJob(
-										"REMOVE_FIRST_QUEUE_SONG",
-										{ stationId: station._id },
-										this
-									).then(() => {
-										next(null, response.song, station);
-									});
-								})
-								.catch(err => {
-									if (err === "No songs available.") next(null, null, station);
-									else next(err);
-								});
-						}
-
-						if (station.type === "community" && !station.partyMode) {
-							StationsModule.runJob(
-								"FILL_UP_STATION_QUEUE_FROM_STATION_PLAYLIST",
-								{ stationId: station._id },
-								this
-							)
-								.then(() => {
-									StationsModule.runJob("GET_NEXT_STATION_SONG", { stationId: station._id }, this)
-										.then(response => {
-											StationsModule.runJob(
-												"REMOVE_FIRST_QUEUE_SONG",
-												{ stationId: station._id },
-												this
-											).then(() => {
+						StationsModule.runJob(
+							"FILL_UP_STATION_QUEUE_FROM_STATION_PLAYLIST",
+							{ stationId: station._id },
+							this
+						)
+							.then(() => {
+								StationsModule.runJob("GET_NEXT_STATION_SONG", { stationId: station._id }, this)
+									.then(response => {
+										StationsModule.runJob(
+											"REMOVE_FIRST_QUEUE_SONG",
+											{ stationId: station._id },
+											this
+										)
+											.then(() => {
 												next(null, response.song, station);
 												next(null, response.song, station);
-											});
-										})
-										.catch(err => {
-											if (err === "No songs available.") next(null, null, station);
-											else next(err);
-										});
-								})
-								.catch(next);
-						}
-
-						if (station.type === "official") {
-							StationsModule.runJob(
-								"FILL_UP_STATION_QUEUE_FROM_STATION_PLAYLIST",
-								{ stationId: station._id },
-								this
-							)
-								.then(() => {
-									StationsModule.runJob("GET_NEXT_STATION_SONG", { stationId: station._id }, this)
-										.then(response => {
-											StationsModule.runJob(
-												"REMOVE_FIRST_QUEUE_SONG",
-												{ stationId: station._id },
-												this
-											)
-												.then(() => {
-													next(null, response.song, station);
-												})
-												.catch(next);
-										})
-										.catch(err => {
-											if (err === "No songs available.") next(null, null, station);
-											else next(err);
-										});
-								})
-								.catch(next);
-						}
+											})
+											.catch(next);
+									})
+									.catch(err => {
+										if (err === "No songs available.") next(null, null, station);
+										else next(err);
+									});
+							})
+							.catch(next);
 					},
 					},
 					(song, station, next) => {
 					(song, station, next) => {
 						const $set = {};
 						const $set = {};

+ 6 - 19
frontend/src/components/Queue.vue

@@ -22,10 +22,7 @@
 				<template #item="{ element, index }">
 				<template #item="{ element, index }">
 					<song-item
 					<song-item
 						:song="element"
 						:song="element"
-						:requested-by="
-							station.type === 'community' &&
-							station.partyMode === true
-						"
+						:requested-by="true"
 						:class="{
 						:class="{
 							'item-draggable': isAdminOnly() || isOwnerOnly()
 							'item-draggable': isAdminOnly() || isOwnerOnly()
 						}"
 						}"
@@ -77,37 +74,27 @@
 			v-if="
 			v-if="
 				sector === 'station' &&
 				sector === 'station' &&
 				loggedIn &&
 				loggedIn &&
-				station.type === 'community' &&
-				station.partyMode &&
 				((station.locked && isOwnerOnly()) ||
 				((station.locked && isOwnerOnly()) ||
 					!station.locked ||
 					!station.locked ||
 					(station.locked && isAdminOnly() && dismissedWarning))
 					(station.locked && isAdminOnly() && dismissedWarning))
 			"
 			"
-			@click="openModal('manageStation') & showManageStationTab('songs')"
+			@click="
+				openModal('manageStation') & showManageStationTab('request')
+			"
 		>
 		>
 			<i class="material-icons icon-with-button">queue</i>
 			<i class="material-icons icon-with-button">queue</i>
 			<span> Add Song To Queue </span>
 			<span> Add Song To Queue </span>
 		</button>
 		</button>
 		<button
 		<button
 			class="button is-primary tab-actionable-button disabled"
 			class="button is-primary tab-actionable-button disabled"
-			v-if="
-				sector === 'station' &&
-				!loggedIn &&
-				((station.type === 'community' &&
-					station.partyMode &&
-					!station.locked) ||
-					station.type === 'official')
-			"
+			v-if="sector === 'station' && !loggedIn && !station.locked"
 			content="Login to add songs to queue"
 			content="Login to add songs to queue"
 			v-tippy="{ theme: 'info' }"
 			v-tippy="{ theme: 'info' }"
 		>
 		>
 			<i class="material-icons icon-with-button">queue</i>
 			<i class="material-icons icon-with-button">queue</i>
 			<span> Add Song To Queue </span>
 			<span> Add Song To Queue </span>
 		</button>
 		</button>
-		<div
-			id="queue-locked"
-			v-if="station.type === 'community' && station.locked"
-		>
+		<div id="queue-locked" v-if="station.locked">
 			<button
 			<button
 				v-if="isAdminOnly() && !isOwnerOnly() && !dismissedWarning"
 				v-if="isAdminOnly() && !isOwnerOnly() && !dismissedWarning"
 				class="button tab-actionable-button"
 				class="button tab-actionable-button"

+ 8 - 14
frontend/src/components/SongItem.vue

@@ -37,17 +37,16 @@
 				>
 				>
 					{{ formatArtists() }}
 					{{ formatArtists() }}
 				</h5>
 				</h5>
-				<p
-					class="song-request-time"
-					v-if="requestedBy && song.requestedBy"
-				>
+				<p class="song-request-time" v-if="requestedBy">
 					Requested by
 					Requested by
 					<strong>
 					<strong>
 						<user-id-to-username
 						<user-id-to-username
+							v-if="song.requestedBy"
 							:key="song._id"
 							:key="song._id"
 							:user-id="song.requestedBy"
 							:user-id="song.requestedBy"
 							:link="true"
 							:link="true"
 						/>
 						/>
+						<span v-else>station</span>
 						{{ formatedRequestedAt }}
 						{{ formatedRequestedAt }}
 						ago
 						ago
 					</strong>
 					</strong>
@@ -222,11 +221,7 @@ export default {
 	},
 	},
 	methods: {
 	methods: {
 		formatRequestedAt() {
 		formatRequestedAt() {
-			if (
-				this.requestedBy &&
-				this.song.requestedBy &&
-				this.song.requestedAt
-			)
+			if (this.requestedBy && this.song.requestedAt)
 				this.formatedRequestedAt = this.formatDistance(
 				this.formatedRequestedAt = this.formatDistance(
 					parseISO(this.song.requestedAt),
 					parseISO(this.song.requestedAt),
 					new Date()
 					new Date()
@@ -301,7 +296,7 @@ export default {
 }
 }
 
 
 .song-item {
 .song-item {
-	min-height: 65px;
+	min-height: 70px;
 
 
 	&:not(:last-of-type) {
 	&:not(:last-of-type) {
 		margin-bottom: 10px;
 		margin-bottom: 10px;
@@ -326,9 +321,9 @@ export default {
 	}
 	}
 
 
 	.thumbnail {
 	.thumbnail {
-		min-width: 65px;
-		width: 65px;
-		height: 65px;
+		min-width: 70px;
+		width: 70px;
+		height: 70px;
 		margin: -7.5px;
 		margin: -7.5px;
 		margin-right: calc(20px - 7.5px);
 		margin-right: calc(20px - 7.5px);
 	}
 	}
@@ -371,7 +366,6 @@ export default {
 
 
 		.song-request-time {
 		.song-request-time {
 			font-size: 12px;
 			font-size: 12px;
-			margin-top: 7px;
 		}
 		}
 	}
 	}
 
 

+ 28 - 346
frontend/src/components/modals/ManageStation/Tabs/Playlists.vue → frontend/src/components/modals/ManageStation/Tabs/Autofill.vue

@@ -10,6 +10,14 @@
 				>
 				>
 					Search
 					Search
 				</button>
 				</button>
+				<button
+					class="button is-default"
+					ref="current-tab"
+					:class="{ selected: tab === 'current' }"
+					@click="showTab('current')"
+				>
+					Current
+				</button>
 				<button
 				<button
 					v-if="station.type === 'community'"
 					v-if="station.type === 'community'"
 					class="button is-default"
 					class="button is-default"
@@ -19,33 +27,6 @@
 				>
 				>
 					My Playlists
 					My Playlists
 				</button>
 				</button>
-				<button
-					class="button is-default"
-					ref="party-tab"
-					:class="{ selected: tab === 'party' }"
-					v-if="isPartyMode()"
-					@click="showTab('party')"
-				>
-					Party
-				</button>
-				<button
-					class="button is-default"
-					ref="included-tab"
-					:class="{ selected: tab === 'included' }"
-					v-if="isPlaylistMode()"
-					@click="showTab('included')"
-				>
-					Included
-				</button>
-				<button
-					class="button is-default"
-					ref="excluded-tab"
-					:class="{ selected: tab === 'excluded' }"
-					v-if="isOwnerOrAdmin()"
-					@click="showTab('excluded')"
-				>
-					Excluded
-				</button>
 			</div>
 			</div>
 			<div class="tab" v-show="tab === 'search'">
 			<div class="tab" v-show="tab === 'search'">
 				<div v-if="featuredPlaylists.length > 0">
 				<div v-if="featuredPlaylists.length > 0">
@@ -59,22 +40,7 @@
 						<template #item-icon>
 						<template #item-icon>
 							<i
 							<i
 								class="material-icons"
 								class="material-icons"
-								v-if="
-									isAllowedToParty() &&
-									isSelected(featuredPlaylist._id)
-								"
-								content="This playlist is currently selected"
-								v-tippy
-							>
-								radio
-							</i>
-							<i
-								class="material-icons"
-								v-else-if="
-									isOwnerOrAdmin() &&
-									isPlaylistMode() &&
-									isIncluded(featuredPlaylist._id)
-								"
+								v-if="isIncluded(featuredPlaylist._id)"
 								content="This playlist is currently included"
 								content="This playlist is currently included"
 								v-tippy
 								v-tippy
 							>
 							>
@@ -82,10 +48,7 @@
 							</i>
 							</i>
 							<i
 							<i
 								class="material-icons excluded-icon"
 								class="material-icons excluded-icon"
-								v-else-if="
-									isOwnerOrAdmin() &&
-									isExcluded(featuredPlaylist._id)
-								"
+								v-else-if="isExcluded(featuredPlaylist._id)"
 								content="This playlist is currently excluded"
 								content="This playlist is currently excluded"
 								v-tippy
 								v-tippy
 							>
 							>
@@ -94,11 +57,7 @@
 							<i
 							<i
 								class="material-icons"
 								class="material-icons"
 								v-else
 								v-else
-								:content="
-									isPartyMode()
-										? 'This playlist is currently not selected or excluded'
-										: 'This playlist is currently not included or excluded'
-								"
+								content="This playlist is currently not included or excluded"
 								v-tippy
 								v-tippy
 							>
 							>
 								play_disabled
 								play_disabled
@@ -114,28 +73,7 @@
 								>play_disabled</i
 								>play_disabled</i
 							>
 							>
 							<quick-confirm
 							<quick-confirm
-								v-if="
-									isPartyMode() &&
-									isSelected(featuredPlaylist._id)
-								"
-								@confirm="
-									deselectPartyPlaylist(featuredPlaylist._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() &&
-									isPlaylistMode() &&
-									isIncluded(featuredPlaylist._id)
-								"
+								v-if="isIncluded(featuredPlaylist._id)"
 								@confirm="
 								@confirm="
 									removeIncludedPlaylist(featuredPlaylist._id)
 									removeIncludedPlaylist(featuredPlaylist._id)
 								"
 								"
@@ -150,20 +88,6 @@
 							</quick-confirm>
 							</quick-confirm>
 							<i
 							<i
 								v-if="
 								v-if="
-									isPartyMode() &&
-									!isSelected(featuredPlaylist._id) &&
-									!isExcluded(featuredPlaylist._id)
-								"
-								@click="selectPartyPlaylist(featuredPlaylist)"
-								class="material-icons play-icon"
-								content="Request songs from this playlist"
-								v-tippy
-								>play_arrow</i
-							>
-							<i
-								v-if="
-									isOwnerOrAdmin() &&
-									isPlaylistMode() &&
 									!isIncluded(featuredPlaylist._id) &&
 									!isIncluded(featuredPlaylist._id) &&
 									!isExcluded(featuredPlaylist._id)
 									!isExcluded(featuredPlaylist._id)
 								"
 								"
@@ -174,10 +98,7 @@
 								>play_arrow</i
 								>play_arrow</i
 							>
 							>
 							<quick-confirm
 							<quick-confirm
-								v-if="
-									isOwnerOrAdmin() &&
-									!isExcluded(featuredPlaylist._id)
-								"
+								v-if="!isExcluded(featuredPlaylist._id)"
 								@confirm="
 								@confirm="
 									blacklistPlaylist(featuredPlaylist._id)
 									blacklistPlaylist(featuredPlaylist._id)
 								"
 								"
@@ -190,10 +111,7 @@
 								>
 								>
 							</quick-confirm>
 							</quick-confirm>
 							<quick-confirm
 							<quick-confirm
-								v-if="
-									isOwnerOrAdmin() &&
-									isExcluded(featuredPlaylist._id)
-								"
+								v-if="isExcluded(featuredPlaylist._id)"
 								@confirm="
 								@confirm="
 									removeExcludedPlaylist(featuredPlaylist._id)
 									removeExcludedPlaylist(featuredPlaylist._id)
 								"
 								"
@@ -259,22 +177,7 @@
 						<template #item-icon>
 						<template #item-icon>
 							<i
 							<i
 								class="material-icons"
 								class="material-icons"
-								v-if="
-									isAllowedToParty() &&
-									isSelected(playlist._id)
-								"
-								content="This playlist is currently selected"
-								v-tippy
-							>
-								radio
-							</i>
-							<i
-								class="material-icons"
-								v-else-if="
-									isOwnerOrAdmin() &&
-									isPlaylistMode() &&
-									isIncluded(playlist._id)
-								"
+								v-if="isIncluded(playlist._id)"
 								content="This playlist is currently included"
 								content="This playlist is currently included"
 								v-tippy
 								v-tippy
 							>
 							>
@@ -282,9 +185,7 @@
 							</i>
 							</i>
 							<i
 							<i
 								class="material-icons excluded-icon"
 								class="material-icons excluded-icon"
-								v-else-if="
-									isOwnerOrAdmin() && isExcluded(playlist._id)
-								"
+								v-else-if="isExcluded(playlist._id)"
 								content="This playlist is currently excluded"
 								content="This playlist is currently excluded"
 								v-tippy
 								v-tippy
 							>
 							>
@@ -293,11 +194,7 @@
 							<i
 							<i
 								class="material-icons"
 								class="material-icons"
 								v-else
 								v-else
-								:content="
-									isPartyMode()
-										? 'This playlist is currently not selected or excluded'
-										: 'This playlist is currently not included or excluded'
-								"
+								content="This playlist is currently not included or excluded"
 								v-tippy
 								v-tippy
 							>
 							>
 								play_disabled
 								play_disabled
@@ -313,23 +210,7 @@
 								>play_disabled</i
 								>play_disabled</i
 							>
 							>
 							<quick-confirm
 							<quick-confirm
-								v-if="isPartyMode() && isSelected(playlist._id)"
-								@confirm="deselectPartyPlaylist(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() &&
-									isPlaylistMode() &&
-									isIncluded(playlist._id)
-								"
+								v-if="isIncluded(playlist._id)"
 								@confirm="removeIncludedPlaylist(playlist._id)"
 								@confirm="removeIncludedPlaylist(playlist._id)"
 							>
 							>
 								<i
 								<i
@@ -342,20 +223,6 @@
 							</quick-confirm>
 							</quick-confirm>
 							<i
 							<i
 								v-if="
 								v-if="
-									isPartyMode() &&
-									!isSelected(playlist._id) &&
-									!isExcluded(playlist._id)
-								"
-								@click="selectPartyPlaylist(playlist)"
-								class="material-icons play-icon"
-								content="Request songs from this playlist"
-								v-tippy
-								>play_arrow</i
-							>
-							<i
-								v-if="
-									isOwnerOrAdmin() &&
-									isPlaylistMode() &&
 									!isIncluded(playlist._id) &&
 									!isIncluded(playlist._id) &&
 									!isExcluded(playlist._id)
 									!isExcluded(playlist._id)
 								"
 								"
@@ -366,10 +233,7 @@
 								>play_arrow</i
 								>play_arrow</i
 							>
 							>
 							<quick-confirm
 							<quick-confirm
-								v-if="
-									isOwnerOrAdmin() &&
-									!isExcluded(playlist._id)
-								"
+								v-if="!isExcluded(playlist._id)"
 								@confirm="blacklistPlaylist(playlist._id)"
 								@confirm="blacklistPlaylist(playlist._id)"
 							>
 							>
 								<i
 								<i
@@ -380,9 +244,7 @@
 								>
 								>
 							</quick-confirm>
 							</quick-confirm>
 							<quick-confirm
 							<quick-confirm
-								v-if="
-									isOwnerOrAdmin() && isExcluded(playlist._id)
-								"
+								v-if="isExcluded(playlist._id)"
 								@confirm="removeExcludedPlaylist(playlist._id)"
 								@confirm="removeExcludedPlaylist(playlist._id)"
 							>
 							>
 								<i
 								<i
@@ -459,22 +321,7 @@
 								<template #item-icon>
 								<template #item-icon>
 									<i
 									<i
 										class="material-icons"
 										class="material-icons"
-										v-if="
-											isAllowedToParty() &&
-											isSelected(element._id)
-										"
-										content="This playlist is currently selected"
-										v-tippy
-									>
-										radio
-									</i>
-									<i
-										class="material-icons"
-										v-else-if="
-											isOwnerOrAdmin() &&
-											isPlaylistMode() &&
-											isIncluded(element._id)
-										"
+										v-if="isIncluded(element._id)"
 										content="This playlist is currently included"
 										content="This playlist is currently included"
 										v-tippy
 										v-tippy
 									>
 									>
@@ -482,10 +329,7 @@
 									</i>
 									</i>
 									<i
 									<i
 										class="material-icons excluded-icon"
 										class="material-icons excluded-icon"
-										v-else-if="
-											isOwnerOrAdmin() &&
-											isExcluded(element._id)
-										"
+										v-else-if="isExcluded(element._id)"
 										content="This playlist is currently excluded"
 										content="This playlist is currently excluded"
 										v-tippy
 										v-tippy
 									>
 									>
@@ -494,11 +338,7 @@
 									<i
 									<i
 										class="material-icons"
 										class="material-icons"
 										v-else
 										v-else
-										:content="
-											isPartyMode()
-												? 'This playlist is currently not selected or excluded'
-												: 'This playlist is currently not included or excluded'
-										"
+										content="This playlist is currently not included or excluded"
 										v-tippy
 										v-tippy
 									>
 									>
 										play_disabled
 										play_disabled
@@ -508,18 +348,6 @@
 								<template #actions>
 								<template #actions>
 									<i
 									<i
 										v-if="
 										v-if="
-											isPartyMode() &&
-											!isSelected(element._id)
-										"
-										@click="selectPartyPlaylist(element)"
-										class="material-icons play-icon"
-										content="Request songs from this playlist"
-										v-tippy
-										>play_arrow</i
-									>
-									<i
-										v-if="
-											isPlaylistMode() &&
 											isOwnerOrAdmin() &&
 											isOwnerOrAdmin() &&
 											!isIncluded(element._id)
 											!isIncluded(element._id)
 										"
 										"
@@ -531,23 +359,6 @@
 									>
 									>
 									<quick-confirm
 									<quick-confirm
 										v-if="
 										v-if="
-											isPartyMode() &&
-											isSelected(element._id)
-										"
-										@confirm="
-											deselectPartyPlaylist(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="
-											isPlaylistMode() &&
 											isOwnerOrAdmin() &&
 											isOwnerOrAdmin() &&
 											isIncluded(element._id)
 											isIncluded(element._id)
 										"
 										"
@@ -612,79 +423,7 @@
 					You don't have any playlists!
 					You don't have any playlists!
 				</p>
 				</p>
 			</div>
 			</div>
-			<div class="tab" v-show="tab === 'party'" v-if="isPartyMode()">
-				<div v-if="partyPlaylists.length > 0">
-					<playlist-item
-						v-for="playlist in partyPlaylists"
-						: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="deselectPartyPlaylist(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
-				class="tab"
-				v-show="tab === 'included'"
-				v-if="isPlaylistMode()"
-			>
+			<div class="tab" v-show="tab === 'current'">
 				<div v-if="includedPlaylists.length > 0">
 				<div v-if="includedPlaylists.length > 0">
 					<playlist-item
 					<playlist-item
 						v-for="playlist in includedPlaylists"
 						v-for="playlist in includedPlaylists"
@@ -752,62 +491,6 @@
 					No playlists currently included.
 					No playlists currently included.
 				</p>
 				</p>
 			</div>
 			</div>
-			<div
-				class="tab"
-				v-show="tab === 'excluded'"
-				v-if="isOwnerOrAdmin()"
-			>
-				<div v-if="excludedPlaylists.length > 0">
-					<playlist-item
-						:playlist="playlist"
-						v-for="playlist in excludedPlaylists"
-						:key="`key-${playlist._id}`"
-					>
-						<template #item-icon>
-							<i
-								class="material-icons excluded-icon"
-								content="This playlist is currently excluded"
-								v-tippy
-							>
-								block
-							</i>
-						</template>
-
-						<template #actions>
-							<quick-confirm
-								@confirm="removeExcludedPlaylist(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 excluded.
-				</p>
-			</div>
 		</div>
 		</div>
 	</div>
 	</div>
 </template>
 </template>
@@ -816,15 +499,15 @@ import { mapActions, mapState, mapGetters } from "vuex";
 import Toast from "toasters";
 import Toast from "toasters";
 import ws from "@/ws";
 import ws from "@/ws";
 
 
-import PlaylistItem from "@/components/PlaylistItem.vue";
 import QuickConfirm from "@/components/QuickConfirm.vue";
 import QuickConfirm from "@/components/QuickConfirm.vue";
+import PlaylistItem from "@/components/PlaylistItem.vue";
 
 
 import SortablePlaylists from "@/mixins/SortablePlaylists.vue";
 import SortablePlaylists from "@/mixins/SortablePlaylists.vue";
 
 
 export default {
 export default {
 	components: {
 	components: {
-		PlaylistItem,
-		QuickConfirm
+		QuickConfirm,
+		PlaylistItem
 	},
 	},
 	mixins: [SortablePlaylists],
 	mixins: [SortablePlaylists],
 	data() {
 	data() {
@@ -879,8 +562,7 @@ export default {
 		}
 		}
 	},
 	},
 	mounted() {
 	mounted() {
-		if (this.station.type === "community" && this.station.partyMode)
-			this.showTab("search");
+		this.showTab("search");
 
 
 		ws.onConnect(this.init);
 		ws.onConnect(this.init);
 	},
 	},

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

@@ -0,0 +1,213 @@
+<template>
+	<div class="station-blacklist">
+		<div class="tabs-container">
+			<div class="tab" v-if="isOwnerOrAdmin()">
+				<div v-if="excludedPlaylists.length > 0">
+					<playlist-item
+						:playlist="playlist"
+						v-for="playlist in excludedPlaylists"
+						:key="`key-${playlist._id}`"
+					>
+						<template #item-icon>
+							<i
+								class="material-icons excluded-icon"
+								content="This playlist is currently excluded"
+								v-tippy
+							>
+								block
+							</i>
+						</template>
+
+						<template #actions>
+							<quick-confirm
+								@confirm="removeExcludedPlaylist(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 excluded.
+				</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,
+			partyPlaylists: state => state.station.partyPlaylists
+		}),
+		...mapState("modals/manageStation", {
+			originalStation: state => state.originalStation,
+			station: state => state.station,
+			includedPlaylists: state => state.includedPlaylists,
+			excludedPlaylists: state => state.excludedPlaylists
+		}),
+		...mapGetters({
+			socket: "websockets/getSocket"
+		})
+	},
+	mounted() {
+		ws.onConnect(this.init);
+	},
+	methods: {
+		init() {
+			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;
+					}
+				}
+			);
+		},
+		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();
+					}
+				);
+			});
+		},
+		removeExcludedPlaylist(id) {
+			return new Promise(resolve => {
+				this.socket.dispatch(
+					"stations.removeExcludedPlaylist",
+					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;
+		},
+		isExcluded(id) {
+			let selected = false;
+			this.excludedPlaylists.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.excludePlaylist",
+				this.station._id,
+				id,
+				res => {
+					new Toast(res.message);
+				}
+			);
+		},
+		...mapActions("modalVisibility", ["openModal"]),
+		...mapActions("user/playlists", ["editPlaylist"])
+	}
+};
+</script>
+
+<style lang="less" scoped>
+.excluded-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>

+ 1078 - 0
frontend/src/components/modals/ManageStation/Tabs/Request.vue

@@ -0,0 +1,1078 @@
+<template>
+	<div class="station-playlists">
+		<div class="tabs-container">
+			<div class="tab-selection">
+				<button
+					class="button is-default"
+					ref="songs-tab"
+					:class="{ selected: tab === 'songs' }"
+					@click="showTab('songs')"
+				>
+					Songs
+				</button>
+				<button
+					class="button is-default"
+					ref="autorequest-tab"
+					:class="{ selected: tab === 'autorequest' }"
+					@click="showTab('autorequest')"
+				>
+					Autorequest
+				</button>
+			</div>
+			<div
+				class="tab"
+				v-show="tab === 'songs'"
+				v-if="isOwnerOrAdmin() || !station.locked"
+			>
+				<div class="musare-songs">
+					<label class="label"> Search for a song on Musare </label>
+					<div class="control is-grouped input-with-button">
+						<p class="control is-expanded">
+							<input
+								class="input"
+								type="text"
+								placeholder="Enter your song query here..."
+								v-model="musareSearch.query"
+								@keyup.enter="searchForMusareSongs(1)"
+							/>
+						</p>
+						<p class="control">
+							<a
+								class="button is-info"
+								@click="searchForMusareSongs(1)"
+								><i class="material-icons icon-with-button"
+									>search</i
+								>Search</a
+							>
+						</p>
+					</div>
+					<div v-if="musareSearch.results.length > 0">
+						<song-item
+							v-for="song in musareSearch.results"
+							:key="song._id"
+							:song="song"
+						>
+							<template #actions>
+								<transition
+									name="musare-search-query-actions"
+									mode="out-in"
+								>
+									<i
+										v-if="
+											songsInQueue.indexOf(
+												song.youtubeId
+											) !== -1
+										"
+										class="material-icons added-to-playlist-icon"
+										content="Song is already in queue"
+										v-tippy
+										>done</i
+									>
+									<i
+										v-else
+										class="material-icons add-to-queue-icon"
+										@click="addSongToQueue(song.youtubeId)"
+										content="Add Song to Queue"
+										v-tippy
+										>queue</i
+									>
+								</transition>
+							</template>
+						</song-item>
+						<button
+							v-if="playlistResultsLeftCount > 0"
+							class="button is-primary load-more-button"
+							@click="searchForMusareSongs(musareSearch.page + 1)"
+						>
+							Load {{ nextPagePlaylistsResultsCount }} more
+							results
+						</button>
+					</div>
+				</div>
+
+				<div class="youtube-search">
+					<label class="label"> Search for a song on YouTube </label>
+					<div class="control is-grouped input-with-button">
+						<p class="control is-expanded">
+							<input
+								class="input"
+								type="text"
+								placeholder="Enter your YouTube query here..."
+								v-model="youtubeSearch.songs.query"
+								autofocus
+								@keyup.enter="searchForSongs()"
+							/>
+						</p>
+						<p class="control">
+							<a
+								class="button is-info"
+								@click.prevent="searchForSongs()"
+								><i class="material-icons icon-with-button"
+									>search</i
+								>Search</a
+							>
+						</p>
+					</div>
+
+					<div
+						v-if="youtubeSearch.songs.results.length > 0"
+						id="song-query-results"
+					>
+						<search-query-item
+							v-for="(result, index) in youtubeSearch.songs
+								.results"
+							:key="result.id"
+							:result="result"
+						>
+							<template #actions>
+								<transition
+									name="youtube-search-query-actions"
+									mode="out-in"
+								>
+									<i
+										v-if="
+											songsInQueue.indexOf(result.id) !==
+											-1
+										"
+										class="material-icons added-to-playlist-icon"
+										content="Song is already in queue"
+										v-tippy
+										>done</i
+									>
+									<i
+										v-else
+										class="material-icons add-to-queue-icon"
+										@click="
+											addSongToQueue(result.id, index)
+										"
+										content="Add Song to Queue"
+										v-tippy
+										>queue</i
+									>
+								</transition>
+							</template>
+						</search-query-item>
+
+						<a
+							class="button is-primary load-more-button"
+							@click.prevent="loadMoreSongs()"
+						>
+							Load more...
+						</a>
+					</div>
+				</div>
+			</div>
+			<div 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
+						v-if="station.type === 'community'"
+						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 excluded-icon"
+									v-else-if="isExcluded(featuredPlaylist._id)"
+									content="This playlist is currently excluded"
+									v-tippy
+								>
+									block
+								</i>
+								<i
+									class="material-icons"
+									v-else
+									content="This playlist is currently not selected or excluded"
+									v-tippy
+								>
+									play_disabled
+								</i>
+							</template>
+
+							<template #actions>
+								<i
+									v-if="isExcluded(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="
+										deselectPartyPlaylist(
+											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) &&
+										!isExcluded(featuredPlaylist._id)
+									"
+									@click="
+										selectPartyPlaylist(featuredPlaylist)
+									"
+									class="material-icons play-icon"
+									content="Request songs from this playlist"
+									v-tippy
+									>play_arrow</i
+								>
+								<quick-confirm
+									v-if="
+										isOwnerOrAdmin() &&
+										!isExcluded(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() &&
+										isExcluded(featuredPlaylist._id)
+									"
+									@confirm="
+										removeExcludedPlaylist(
+											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 excluded-icon"
+									v-else-if="isExcluded(playlist._id)"
+									content="This playlist is currently excluded"
+									v-tippy
+								>
+									block
+								</i>
+								<i
+									class="material-icons"
+									v-else
+									content="This playlist is currently not selected or excluded"
+									v-tippy
+								>
+									play_disabled
+								</i>
+							</template>
+
+							<template #actions>
+								<i
+									v-if="isExcluded(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="
+										deselectPartyPlaylist(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) &&
+										!isExcluded(playlist._id)
+									"
+									@click="selectPartyPlaylist(playlist)"
+									class="material-icons play-icon"
+									content="Request songs from this playlist"
+									v-tippy
+									>play_arrow</i
+								>
+								<quick-confirm
+									v-if="
+										isOwnerOrAdmin() &&
+										!isExcluded(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() &&
+										isExcluded(playlist._id)
+									"
+									@confirm="
+										removeExcludedPlaylist(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
+					v-if="station.type === 'community'"
+					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 excluded-icon"
+											v-else-if="isExcluded(element._id)"
+											content="This playlist is currently excluded"
+											v-tippy
+										>
+											block
+										</i>
+										<i
+											class="material-icons"
+											v-else
+											content="This playlist is currently not selected or excluded"
+											v-tippy
+										>
+											play_disabled
+										</i>
+									</template>
+
+									<template #actions>
+										<i
+											v-if="!isSelected(element._id)"
+											@click="
+												selectPartyPlaylist(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="
+												deselectPartyPlaylist(
+													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() &&
+												!isExcluded(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() &&
+												isExcluded(element._id)
+											"
+											@confirm="
+												removeExcludedPlaylist(
+													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="partyPlaylists.length > 0">
+						<playlist-item
+							v-for="playlist in partyPlaylists"
+							: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="
+										deselectPartyPlaylist(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>
+		</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 SongItem from "@/components/SongItem.vue";
+import PlaylistItem from "@/components/PlaylistItem.vue";
+import SearchQueryItem from "../../../SearchQueryItem.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
+	},
+	mixins: [SortablePlaylists, SearchYoutube, SearchMusare],
+	data() {
+		return {
+			tab: "songs",
+			childTab: "search",
+			playlistSearch: {
+				query: "",
+				searchedQuery: "",
+				page: 0,
+				count: 0,
+				resultsLeft: 0,
+				results: []
+			},
+			featuredPlaylists: []
+		};
+	},
+	computed: {
+		playlistResultsLeftCount() {
+			return (
+				this.playlistSearch.count - this.playlistSearch.results.length
+			);
+		},
+		nextPagePlaylistSearchResultsCount() {
+			return Math.min(
+				this.playlistSearch.pageSize,
+				this.playlistResultsLeftCount
+			);
+		},
+		songsInQueue() {
+			if (this.station.currentSong)
+				return this.songsList
+					.map(song => song.youtubeId)
+					.concat(this.station.currentSong.youtubeId);
+			return this.songsList.map(song => song.youtubeId);
+		},
+		...mapState({
+			loggedIn: state => state.user.auth.loggedIn,
+			role: state => state.user.auth.role,
+			userId: state => state.user.auth.userId,
+			partyPlaylists: state => state.station.partyPlaylists
+		}),
+		...mapState("modals/manageStation", {
+			parentTab: state => state.tab,
+			originalStation: state => state.originalStation,
+			station: state => state.station,
+			includedPlaylists: state => state.includedPlaylists,
+			excludedPlaylists: state => state.excludedPlaylists,
+			songsList: state => state.songsList
+		}),
+		...mapGetters({
+			socket: "websockets/getSocket"
+		})
+	},
+	mounted() {
+		this.showTab("songs");
+
+		ws.onConnect(this.init);
+	},
+	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.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;
+					}
+				}
+			);
+		},
+		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");
+		},
+		selectPartyPlaylist(playlist) {
+			if (!this.isSelected(playlist.id)) {
+				this.partyPlaylists.push(playlist);
+				this.addPartyPlaylistSongToQueue();
+				new Toast(
+					"Successfully selected playlist to auto request songs."
+				);
+			} else {
+				new Toast("Error: Playlist already selected.");
+			}
+		},
+		deselectPartyPlaylist(id) {
+			return new Promise(resolve => {
+				let selected = false;
+				this.partyPlaylists.forEach((playlist, index) => {
+					if (playlist._id === id) {
+						selected = true;
+						this.partyPlaylists.splice(index, 1);
+					}
+				});
+				if (selected) {
+					new Toast("Successfully deselected playlist.");
+					resolve();
+				} else {
+					new Toast("Playlist not selected.");
+					resolve();
+				}
+			});
+		},
+		removeExcludedPlaylist(id) {
+			return new Promise(resolve => {
+				this.socket.dispatch(
+					"stations.removeExcludedPlaylist",
+					this.station._id,
+					id,
+					res => {
+						new Toast(res.message);
+						resolve();
+					}
+				);
+			});
+		},
+		isSelected(id) {
+			let selected = false;
+			this.partyPlaylists.forEach(playlist => {
+				if (playlist._id === id) selected = true;
+			});
+			return selected;
+		},
+		isExcluded(id) {
+			let selected = false;
+			this.excludedPlaylists.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.excludePlaylist",
+				this.station._id,
+				id,
+				res => {
+					new Toast(res.message);
+				}
+			);
+		},
+		addPartyPlaylistSongToQueue() {
+			if (
+				this.songsList.length < 50 &&
+				this.songsList.filter(
+					queueSong => queueSong.requestedBy === this.userId
+				).length < 3 &&
+				this.partyPlaylists
+			) {
+				const selectedPlaylist =
+					this.partyPlaylists[
+						Math.floor(Math.random() * this.partyPlaylists.length)
+					];
+				if (selectedPlaylist._id && selectedPlaylist.songs.length > 0) {
+					const selectedSong =
+						selectedPlaylist.songs[
+							Math.floor(
+								Math.random() * selectedPlaylist.songs.length
+							)
+						];
+					if (selectedSong.youtubeId) {
+						this.socket.dispatch(
+							"stations.addToQueue",
+							this.station._id,
+							selectedSong.youtubeId,
+							data => {
+								if (data.status !== "success")
+									this.addPartyPlaylistSongToQueue();
+							}
+						);
+					}
+				}
+			}
+		},
+		addSongToQueue(youtubeId, index) {
+			this.socket.dispatch(
+				"stations.addToQueue",
+				this.station._id,
+				youtubeId,
+				res => {
+					if (res.status !== "success")
+						new Toast(`Error: ${res.message}`);
+					else {
+						if (index)
+							this.youtubeSearch.songs.results[
+								index
+							].isAddedToQueue = true;
+
+						new Toast(res.message);
+					}
+				}
+			);
+		},
+		...mapActions("station", ["updatePartyPlaylists"]),
+		...mapActions("modalVisibility", ["openModal"]),
+		...mapActions("user/playlists", ["editPlaylist", "setPlaylists"])
+	}
+};
+</script>
+
+<style lang="less" scoped>
+.night-mode {
+	.tabs-container .tab-selection .button {
+		background: var(--dark-grey) !important;
+		color: var(--white) !important;
+	}
+}
+
+.excluded-icon {
+	color: var(--dark-red);
+}
+
+.included-icon {
+	color: var(--green);
+}
+
+.selected-icon {
+	color: var(--purple);
+}
+
+.station-playlists {
+	.tabs-container {
+		.tab-selection {
+			display: flex;
+			overflow-x: auto;
+
+			.button {
+				border-radius: 0;
+				border: 0;
+				text-transform: uppercase;
+				font-size: 14px;
+				color: var(--dark-grey-3);
+				background-color: var(--light-grey-2);
+				flex-grow: 1;
+				height: 32px;
+
+				&:not(:first-of-type) {
+					margin-left: 5px;
+				}
+			}
+
+			.selected {
+				background-color: var(--primary-color) !important;
+				color: var(--white) !important;
+				font-weight: 600;
+			}
+		}
+		& > div > .tab-selection .button {
+			margin-top: 5px;
+			font-size: 12px;
+			height: 28px;
+		}
+		.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;
+			}
+		}
+	}
+}
+.draggable-list-transition-move {
+	transition: transform 0.5s;
+}
+
+.draggable-list-ghost {
+	opacity: 0.5;
+	filter: brightness(95%);
+}
+</style>

+ 200 - 288
frontend/src/components/modals/ManageStation/Tabs/Settings.vue

@@ -9,6 +9,7 @@
 				<a class="button is-info" @click.prevent="updateName()">Save</a>
 				<a class="button is-info" @click.prevent="updateName()">Save</a>
 			</p>
 			</p>
 		</div>
 		</div>
+
 		<label class="label">Display Name</label>
 		<label class="label">Display Name</label>
 		<div class="control is-grouped input-with-button">
 		<div class="control is-grouped input-with-button">
 			<p class="control is-expanded">
 			<p class="control is-expanded">
@@ -24,6 +25,7 @@
 				>
 				>
 			</p>
 			</p>
 		</div>
 		</div>
+
 		<label class="label">Description</label>
 		<label class="label">Description</label>
 		<div class="control is-grouped input-with-button">
 		<div class="control is-grouped input-with-button">
 			<p class="control is-expanded">
 			<p class="control is-expanded">
@@ -43,282 +45,196 @@
 		<div class="settings-buttons">
 		<div class="settings-buttons">
 			<div class="small-section">
 			<div class="small-section">
 				<label class="label">Theme</label>
 				<label class="label">Theme</label>
-				<div class="button-wrapper">
-					<tippy
-						theme="stationSettings"
-						:interactive="true"
-						:touch="true"
-						placement="bottom"
-						trigger="click"
-						append-to="parent"
-					>
-						<button :class="station.theme">
-							<i class="material-icons">palette</i>
-							{{ station.theme }}
-						</button>
-
-						<template #content>
-							<button
-								class="blue"
-								v-if="station.theme !== 'blue'"
-								@click="updateTheme('blue')"
-							>
-								<i class="material-icons">palette</i>
-								Blue
-							</button>
-							<button
-								class="purple"
-								v-if="station.theme !== 'purple'"
-								@click="updateTheme('purple')"
-							>
-								<i class="material-icons">palette</i>
-								Purple
-							</button>
-							<button
-								class="teal"
-								v-if="station.theme !== 'teal'"
-								@click="updateTheme('teal')"
-							>
-								<i class="material-icons">palette</i>
-								Teal
-							</button>
-							<button
-								class="orange"
-								v-if="station.theme !== 'orange'"
-								@click="updateTheme('orange')"
-							>
-								<i class="material-icons">palette</i>
-								Orange
-							</button>
-							<button
-								class="red"
-								v-if="station.theme !== 'red'"
-								@click="updateTheme('red')"
-							>
-								<i class="material-icons">palette</i>
-								Red
-							</button>
-						</template>
-					</tippy>
+				<div class="control is-grouped input-with-button">
+					<p class="control is-expanded select">
+						<select v-model="station.theme">
+							<option value="blue" selected>Blue</option>
+							<option value="purple">Purple</option>
+							<option value="teal">Teal</option>
+							<option value="orange">Orange</option>
+							<option value="red">Red</option>
+						</select>
+					</p>
+					<p class="control">
+						<a class="button is-info" @click.prevent="updateTheme()"
+							>Save</a
+						>
+					</p>
 				</div>
 				</div>
 			</div>
 			</div>
 
 
 			<div class="small-section">
 			<div class="small-section">
 				<label class="label">Privacy</label>
 				<label class="label">Privacy</label>
-				<div class="button-wrapper">
-					<tippy
-						theme="stationSettings"
-						:interactive="true"
-						:touch="true"
-						placement="bottom"
-						trigger="click"
-						append-to="parent"
-					>
-						<button :class="privacyButtons[station.privacy].style">
-							<i class="material-icons">{{
-								privacyButtons[station.privacy].iconName
-							}}</i>
-							{{ station.privacy }}
-						</button>
-
-						<template #content>
-							<button
-								class="green"
-								v-if="station.privacy !== 'public'"
-								@click="updatePrivacy('public')"
-							>
-								<i class="material-icons">{{
-									privacyButtons["public"].iconName
-								}}</i>
-								Public
-							</button>
-							<button
-								class="orange"
-								v-if="station.privacy !== 'unlisted'"
-								@click="updatePrivacy('unlisted')"
-							>
-								<i class="material-icons">{{
-									privacyButtons["unlisted"].iconName
-								}}</i>
-								Unlisted
-							</button>
-							<button
-								class="red"
-								v-if="station.privacy !== 'private'"
-								@click="updatePrivacy('private')"
-							>
-								<i class="material-icons">{{
-									privacyButtons["private"].iconName
-								}}</i>
-								Private
-							</button>
-						</template>
-					</tippy>
+				<div class="control is-grouped input-with-button">
+					<p class="control is-expanded select">
+						<select v-model="station.privacy">
+							<option value="public">Public</option>
+							<option value="unlisted">Unlisted</option>
+							<option value="private" selected>Private</option>
+						</select>
+					</p>
+					<p class="control">
+						<a
+							class="button is-info"
+							@click.prevent="updatePrivacy()"
+							>Save</a
+						>
+					</p>
 				</div>
 				</div>
 			</div>
 			</div>
 
 
-			<div class="small-section">
-				<label class="label">Station Mode</label>
-				<div class="button-wrapper" v-if="station.type === 'community'">
-					<tippy
-						theme="stationSettings"
-						:interactive="true"
-						:touch="true"
-						placement="bottom"
-						trigger="click"
-						append-to="parent"
+			<hr class="section-horizontal-rule" />
+
+			<div
+				class="queue-settings"
+				:class="{ enabled: queue.enabled }"
+				style="
+					display: flex;
+					flex-wrap: wrap;
+					width: 100%;
+					margin: 5px 0;
+				"
+			>
+				<div style="display: flex; width: 100%">
+					<label class="label" style="display: flex; flex-grow: 1"
+						>Queue requests</label
 					>
 					>
-						<button
-							:class="{
-								blue: !station.partyMode,
-								yellow: station.partyMode
-							}"
-						>
-							<i class="material-icons">{{
-								station.partyMode
-									? "emoji_people"
-									: "playlist_play"
-							}}</i>
-							{{ station.partyMode ? "Party" : "Playlist" }}
-						</button>
-
-						<template #content>
-							<button
-								class="blue"
-								v-if="station.partyMode"
-								@click="updatePartyMode(false)"
-							>
-								<i class="material-icons">playlist_play</i>
-								Playlist
-							</button>
-							<button
-								class="yellow"
-								v-if="!station.partyMode"
-								@click="updatePartyMode(true)"
-							>
-								<i class="material-icons">emoji_people</i>
-								Party
-							</button>
-						</template>
-					</tippy>
-				</div>
-				<div v-else class="button-wrapper">
-					<button
-						class="blue"
-						content="Can not be changed on official stations."
-						v-tippy="{ theme: 'info' }"
+					<p
+						class="is-expanded checkbox-control"
+						style="justify-content: end"
 					>
 					>
-						<i class="material-icons">playlist_play</i>
-						Playlist
-					</button>
+						<label class="switch">
+							<input
+								type="checkbox"
+								id="toggle-queue"
+								v-model="queue.enabled"
+							/>
+							<span class="slider round"></span>
+						</label>
+
+						<label for="toggle-queue">
+							<p>
+								{{ queue.enabled ? "Disable" : "Enable" }}
+							</p>
+						</label>
+					</p>
 				</div>
 				</div>
-			</div>
 
 
-			<div v-if="!station.partyMode" class="small-section">
-				<label class="label">Play Mode</label>
-				<div class="button-wrapper" v-if="station.type === 'community'">
-					<tippy
-						theme="stationSettings"
-						:interactive="true"
-						:touch="true"
-						placement="bottom"
-						trigger="click"
-						append-to="parent"
-					>
-						<button class="blue">
-							<i class="material-icons">{{
-								station.playMode === "random"
-									? "shuffle"
-									: "format_list_numbered"
-							}}</i>
-							{{
-								station.playMode === "random"
-									? "Random"
-									: "Sequential"
-							}}
-						</button>
-
-						<template #content>
-							<button
-								class="blue"
-								v-if="station.playMode === 'sequential'"
-								@click="updatePlayMode('random')"
-							>
-								<i class="material-icons">shuffle</i>
-								Random
-							</button>
-							<button
-								class="blue"
-								v-if="station.playMode === 'random'"
-								@click="updatePlayMode('sequential')"
+				<div v-if="queue.enabled" class="small-section">
+					<label class="label">Minimum access</label>
+					<div class="control is-grouped input-with-button">
+						<p class="control is-expanded select">
+							<select v-model="queue.access">
+								<option value="owner" selected>Owner</option>
+								<option value="user">User</option>
+							</select>
+						</p>
+						<p class="control">
+							<a
+								class="button is-info"
+								@click.prevent="updateQueuePrivacy()"
+								>Save</a
 							>
 							>
-								<i class="material-icons"
-									>format_list_numbered</i
-								>
-								Sequential
-							</button>
-						</template>
-					</tippy>
+						</p>
+					</div>
 				</div>
 				</div>
-				<div v-else class="button-wrapper">
-					<button
-						class="blue"
-						content="Can not be changed on official stations."
-						v-tippy="{ theme: 'info' }"
-					>
-						<i class="material-icons">shuffle</i>
-						Random
-					</button>
+
+				<div v-if="queue.enabled" class="small-section">
+					<label class="label">Lock</label>
+					<div class="control is-grouped input-with-button">
+						<p class="control is-expanded select">
+							<select v-model="station.locked">
+								<option value="true">Locked</option>
+								<option value="false" selected>Unlocked</option>
+							</select>
+						</p>
+						<p class="control">
+							<a
+								class="button is-info"
+								@click.prevent="updateQueueLock()"
+								>Save</a
+							>
+						</p>
+					</div>
 				</div>
 				</div>
 			</div>
 			</div>
 
 
+			<hr class="section-horizontal-rule" />
+
 			<div
 			<div
-				v-if="
-					station.type === 'community' && station.partyMode === true
+				class="autofill-settings"
+				:class="{ enabled: autofill.enabled }"
+				style="
+					display: flex;
+					flex-wrap: wrap;
+					width: 100%;
+					margin: 5px 0;
 				"
 				"
-				class="small-section"
 			>
 			>
-				<label class="label">Queue lock</label>
-				<div class="button-wrapper">
-					<tippy
-						theme="stationSettings"
-						:interactive="true"
-						:touch="true"
-						placement="bottom"
-						trigger="click"
-						append-to="parent"
+				<div style="display: flex; width: 100%">
+					<label class="label" style="display: flex; flex-grow: 1"
+						>Autofill</label
 					>
 					>
-						<button
-							:class="{
-								green: station.locked,
-								red: !station.locked
-							}"
-						>
-							<i class="material-icons">{{
-								station.locked ? "lock" : "lock_open"
-							}}</i>
-							{{ station.locked ? "Locked" : "Unlocked" }}
-						</button>
-
-						<template #content>
-							<button
-								class="green"
-								v-if="!station.locked"
-								@click="updateQueueLock(true)"
+					<p
+						class="is-expanded checkbox-control"
+						style="justify-content: end"
+					>
+						<label class="switch">
+							<input
+								type="checkbox"
+								id="toggle-autofill"
+								v-model="autofill.enabled"
+							/>
+							<span class="slider round"></span>
+						</label>
+
+						<label for="toggle-autofill">
+							<p>
+								{{ autofill.enabled ? "Disable" : "Enable" }}
+							</p>
+						</label>
+					</p>
+				</div>
+
+				<div v-if="autofill.enabled" class="small-section">
+					<label class="label">Song limit</label>
+					<div class="control is-grouped input-with-button">
+						<p class="control is-expanded">
+							<input
+								class="input"
+								type="number"
+								min="1"
+								max="30"
+								v-model="autofill.limit"
+							/>
+						</p>
+						<p class="control">
+							<a
+								class="button is-info"
+								@click.prevent="updateAutofillLimit()"
+								>Save</a
 							>
 							>
-								<i class="material-icons">lock</i>
-								Locked
-							</button>
-							<button
-								class="red"
-								v-if="station.locked"
-								@click="updateQueueLock(false)"
+						</p>
+					</div>
+				</div>
+
+				<div v-if="autofill.enabled" class="small-section">
+					<label class="label">Play mode</label>
+					<div class="control is-grouped input-with-button">
+						<p class="control is-expanded select">
+							<select v-model="station.playMode">
+								<option value="random" selected>Random</option>
+								<option value="sequential">Sequential</option>
+							</select>
+						</p>
+						<p class="control">
+							<a
+								class="button is-info"
+								@click.prevent="updatePlayMode()"
+								>Save</a
 							>
 							>
-								<i class="material-icons">lock_open</i>
-								Unlocked
-							</button>
-						</template>
-					</tippy>
+						</p>
+					</div>
 				</div>
 				</div>
 			</div>
 			</div>
 		</div>
 		</div>
@@ -348,6 +264,14 @@ export default {
 					style: "orange",
 					style: "orange",
 					iconName: "link"
 					iconName: "link"
 				}
 				}
+			},
+			autofill: {
+				enabled: true,
+				limit: 30
+			},
+			queue: {
+				enabled: true,
+				access: "owner"
 			}
 			}
 		};
 		};
 	},
 	},
@@ -450,76 +374,57 @@ export default {
 				new Toast("Please make a change before saving.");
 				new Toast("Please make a change before saving.");
 			}
 			}
 		},
 		},
-		updateTheme(theme) {
-			if (this.station.theme !== theme) {
+		updateTheme() {
+			if (this.station.theme !== this.originalStation.theme) {
 				this.socket.dispatch(
 				this.socket.dispatch(
 					"stations.updateTheme",
 					"stations.updateTheme",
 					this.station._id,
 					this.station._id,
-					theme,
+					this.station.theme,
 					res => {
 					res => {
 						new Toast(res.message);
 						new Toast(res.message);
 
 
 						if (res.status === "success") {
 						if (res.status === "success") {
-							this.station.theme = theme;
-							this.originalStation.theme = theme;
+							this.originalStation.theme = this.station.theme;
 						}
 						}
 					}
 					}
 				);
 				);
 			}
 			}
 		},
 		},
-		updatePrivacy(privacy) {
-			if (this.station.privacy !== privacy) {
+		updatePrivacy() {
+			if (this.station.privacy !== this.originalStation.privacy) {
 				this.socket.dispatch(
 				this.socket.dispatch(
 					"stations.updatePrivacy",
 					"stations.updatePrivacy",
 					this.station._id,
 					this.station._id,
-					privacy,
-					res => {
-						new Toast(res.message);
-
-						if (res.status === "success") {
-							this.station.privacy = privacy;
-							this.originalStation.privacy = privacy;
-						}
-					}
-				);
-			}
-		},
-		updatePartyMode(partyMode) {
-			if (this.station.partyMode !== partyMode) {
-				this.socket.dispatch(
-					"stations.updatePartyMode",
-					this.station._id,
-					partyMode,
+					this.station.privacy,
 					res => {
 					res => {
 						new Toast(res.message);
 						new Toast(res.message);
 
 
 						if (res.status === "success") {
 						if (res.status === "success") {
-							this.station.partyMode = partyMode;
-							this.originalStation.partyMode = partyMode;
+							this.originalStation.privacy = this.station.privacy;
 						}
 						}
 					}
 					}
 				);
 				);
 			}
 			}
 		},
 		},
-		updatePlayMode(playMode) {
-			if (this.station.playMode !== playMode) {
+		updatePlayMode() {
+			if (this.station.playMode !== this.originalStation.playMode) {
 				this.socket.dispatch(
 				this.socket.dispatch(
 					"stations.updatePlayMode",
 					"stations.updatePlayMode",
 					this.station._id,
 					this.station._id,
-					playMode,
+					this.station.playMode,
 					res => {
 					res => {
 						new Toast(res.message);
 						new Toast(res.message);
 
 
 						if (res.status === "success") {
 						if (res.status === "success") {
-							this.station.playMode = playMode;
-							this.originalStation.playMode = playMode;
+							this.originalStation.playMode =
+								this.station.playMode;
 						}
 						}
 					}
 					}
 				);
 				);
 			}
 			}
 		},
 		},
-		updateQueueLock(locked) {
-			if (this.station.locked !== locked) {
+		updateQueueLock() {
+			if (this.station.locked !== this.originalStation.locked) {
 				this.socket.dispatch(
 				this.socket.dispatch(
 					"stations.toggleLock",
 					"stations.toggleLock",
 					this.station._id,
 					this.station._id,
@@ -539,7 +444,9 @@ export default {
 					}
 					}
 				);
 				);
 			}
 			}
-		}
+		},
+		updateAutofillLimit() {},
+		updateQueuePrivacy() {}
 	}
 	}
 };
 };
 </script>
 </script>
@@ -550,11 +457,16 @@ export default {
 		display: flex;
 		display: flex;
 		justify-content: center;
 		justify-content: center;
 		flex-wrap: wrap;
 		flex-wrap: wrap;
+
 		.small-section {
 		.small-section {
 			width: calc(50% - 10px);
 			width: calc(50% - 10px);
 			min-width: 150px;
 			min-width: 150px;
 			margin: 5px auto;
 			margin: 5px auto;
 		}
 		}
+
+		.section-horizontal-rule {
+			width: 100%;
+		}
 	}
 	}
 	.button-wrapper {
 	.button-wrapper {
 		display: flex;
 		display: flex;

+ 0 - 453
frontend/src/components/modals/ManageStation/Tabs/Songs.vue

@@ -1,453 +0,0 @@
-<template>
-	<div class="songs">
-		<div class="tabs-container">
-			<div class="tab-selection">
-				<button
-					class="button is-default"
-					:class="{ selected: tab === 'search' }"
-					v-if="isAllowedToParty()"
-					@click="showTab('search')"
-				>
-					Search
-				</button>
-				<button
-					class="button is-default"
-					:class="{ selected: tab === 'included' }"
-					v-if="isOwnerOrAdmin() && isPlaylistMode()"
-					@click="showTab('included')"
-				>
-					Included
-				</button>
-				<button
-					class="button is-default"
-					:class="{ selected: tab === 'excluded' }"
-					v-if="isOwnerOrAdmin()"
-					@click="showTab('excluded')"
-				>
-					Excluded
-				</button>
-			</div>
-			<div
-				class="tab"
-				v-show="tab === 'search'"
-				v-if="
-					station.type === 'community' &&
-					station.partyMode &&
-					(isOwnerOrAdmin() || !station.locked)
-				"
-			>
-				<div class="musare-songs">
-					<label class="label"> Search for a song on Musare </label>
-					<div class="control is-grouped input-with-button">
-						<p class="control is-expanded">
-							<input
-								class="input"
-								type="text"
-								placeholder="Enter your song query here..."
-								v-model="musareSearch.query"
-								@keyup.enter="searchForMusareSongs(1)"
-							/>
-						</p>
-						<p class="control">
-							<a
-								class="button is-info"
-								@click="searchForMusareSongs(1)"
-								><i class="material-icons icon-with-button"
-									>search</i
-								>Search</a
-							>
-						</p>
-					</div>
-					<div v-if="musareSearch.results.length > 0">
-						<song-item
-							v-for="song in musareSearch.results"
-							:key="song._id"
-							:song="song"
-						>
-							<template #actions>
-								<transition
-									name="musare-search-query-actions"
-									mode="out-in"
-								>
-									<i
-										v-if="
-											songsInQueue.indexOf(
-												song.youtubeId
-											) !== -1
-										"
-										class="material-icons added-to-playlist-icon"
-										content="Song is already in queue"
-										v-tippy
-										>done</i
-									>
-									<i
-										v-else
-										class="material-icons add-to-queue-icon"
-										@click="addSongToQueue(song.youtubeId)"
-										content="Add Song to Queue"
-										v-tippy
-										>queue</i
-									>
-								</transition>
-							</template>
-						</song-item>
-						<button
-							v-if="resultsLeftCount > 0"
-							class="button is-primary load-more-button"
-							@click="searchForMusareSongs(musareSearch.page + 1)"
-						>
-							Load {{ nextPageResultsCount }} more results
-						</button>
-					</div>
-				</div>
-
-				<div class="youtube-search">
-					<label class="label"> Search for a song on YouTube </label>
-					<div class="control is-grouped input-with-button">
-						<p class="control is-expanded">
-							<input
-								class="input"
-								type="text"
-								placeholder="Enter your YouTube query here..."
-								v-model="youtubeSearch.songs.query"
-								autofocus
-								@keyup.enter="searchForSongs()"
-							/>
-						</p>
-						<p class="control">
-							<a
-								class="button is-info"
-								@click.prevent="searchForSongs()"
-								><i class="material-icons icon-with-button"
-									>search</i
-								>Search</a
-							>
-						</p>
-					</div>
-
-					<div
-						v-if="youtubeSearch.songs.results.length > 0"
-						id="song-query-results"
-					>
-						<search-query-item
-							v-for="(result, index) in youtubeSearch.songs
-								.results"
-							:key="result.id"
-							:result="result"
-						>
-							<template #actions>
-								<transition
-									name="youtube-search-query-actions"
-									mode="out-in"
-								>
-									<i
-										v-if="
-											songsInQueue.indexOf(result.id) !==
-											-1
-										"
-										class="material-icons added-to-playlist-icon"
-										content="Song is already in queue"
-										v-tippy
-										>done</i
-									>
-									<i
-										v-else
-										class="material-icons add-to-queue-icon"
-										@click="
-											addSongToQueue(result.id, index)
-										"
-										content="Add Song to Queue"
-										v-tippy
-										>queue</i
-									>
-								</transition>
-							</template>
-						</search-query-item>
-
-						<a
-							class="button is-primary load-more-button"
-							@click.prevent="loadMoreSongs()"
-						>
-							Load more...
-						</a>
-					</div>
-				</div>
-			</div>
-			<div
-				class="tab"
-				v-show="tab === 'included'"
-				v-if="
-					isOwnerOrAdmin() &&
-					!(station.type === 'community' && station.partyMode)
-				"
-			>
-				<div v-if="stationPlaylist.songs.length > 0">
-					<div id="playlist-info-section">
-						<h5>Song Count: {{ stationPlaylist.songs.length }}</h5>
-						<h5>Duration: {{ totalLength(stationPlaylist) }}</h5>
-					</div>
-					<song-item
-						v-for="song in stationPlaylist.songs"
-						:key="song._id"
-						:song="song"
-					>
-					</song-item>
-				</div>
-				<p v-else class="has-text-centered scrollable-list">
-					No songs currently included. To include songs, include a
-					playlist.
-				</p>
-			</div>
-			<div
-				class="tab"
-				v-show="tab === 'excluded'"
-				v-if="isOwnerOrAdmin()"
-			>
-				<div v-if="excludedSongs.length > 0">
-					<div id="playlist-info-section" class="section">
-						<h5>Song Count: {{ excludedSongs.length }}</h5>
-					</div>
-					<song-item
-						v-for="song in excludedSongs"
-						:key="song._id"
-						:song="song"
-					>
-					</song-item>
-				</div>
-				<p v-else class="has-text-centered scrollable-list">
-					No songs currently excluded. To excluded songs, exclude a
-					playlist.
-				</p>
-			</div>
-		</div>
-	</div>
-</template>
-
-<script>
-import { mapState, mapGetters } from "vuex";
-
-import Toast from "toasters";
-import SearchYoutube from "@/mixins/SearchYoutube.vue";
-import SearchMusare from "@/mixins/SearchMusare.vue";
-
-import SongItem from "@/components/SongItem.vue";
-import SearchQueryItem from "../../../SearchQueryItem.vue";
-
-import utils from "../../../../../js/utils";
-
-export default {
-	components: {
-		SongItem,
-		SearchQueryItem
-	},
-	mixins: [SearchYoutube, SearchMusare],
-	data() {
-		return {
-			utils,
-			tab: "search"
-		};
-	},
-	computed: {
-		excludedSongs() {
-			return this.excludedPlaylists
-				.map(playlist => playlist.songs)
-				.flat()
-				.filter((song, index, self) => self.indexOf(song) === index);
-		},
-		excludedSongIds() {
-			return this.excludedSongs.map(excludedSong => excludedSong._id);
-		},
-		songsInQueue() {
-			if (this.station.currentSong)
-				return this.songsList
-					.map(song => song.youtubeId)
-					.concat(this.station.currentSong.youtubeId);
-			return this.songsList.map(song => song.youtubeId);
-		},
-		...mapState({
-			loggedIn: state => state.user.auth.loggedIn,
-			userId: state => state.user.auth.userId,
-			role: state => state.user.auth.role
-		}),
-		...mapState("modals/manageStation", {
-			parentTab: state => state.tab,
-			station: state => state.station,
-			originalStation: state => state.originalStation,
-			songsList: state => state.songsList,
-			excludedPlaylists: state => state.excludedPlaylists,
-			stationPlaylist: state => state.stationPlaylist
-		}),
-		...mapGetters({
-			socket: "websockets/getSocket"
-		})
-	},
-	watch: {
-		// eslint-disable-next-line func-names
-		parentTab(value) {
-			if (value === "songs") {
-				if (this.tab === "search" && this.isPlaylistMode()) {
-					this.showTab("included");
-				} else if (this.tab === "included" && this.isPartyMode()) {
-					this.showTab("search");
-				}
-			}
-		}
-	},
-	methods: {
-		showTab(tab) {
-			this.tab = 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();
-		},
-		isPartyMode() {
-			return (
-				this.station &&
-				this.station.type === "community" &&
-				this.station.partyMode
-			);
-		},
-		isAllowedToParty() {
-			return (
-				this.station &&
-				this.isPartyMode() &&
-				(!this.station.locked || this.isOwnerOrAdmin()) &&
-				this.loggedIn
-			);
-		},
-		isPlaylistMode() {
-			return this.station && !this.isPartyMode();
-		},
-		totalLength(playlist) {
-			let length = 0;
-			playlist.songs.forEach(song => {
-				length += song.duration;
-			});
-			return this.utils.formatTimeLong(length);
-		},
-		addSongToQueue(youtubeId, index) {
-			if (this.station.type === "community") {
-				this.socket.dispatch(
-					"stations.addToQueue",
-					this.station._id,
-					youtubeId,
-					res => {
-						if (res.status !== "success")
-							new Toast(`Error: ${res.message}`);
-						else {
-							if (index)
-								this.youtubeSearch.songs.results[
-									index
-								].isAddedToQueue = true;
-
-							new Toast(res.message);
-						}
-					}
-				);
-			} else {
-				this.socket.dispatch("songs.request", youtubeId, res => {
-					if (res.status !== "success")
-						new Toast(`Error: ${res.message}`);
-					else {
-						this.youtubeSearch.songs.results[
-							index
-						].isAddedToQueue = true;
-
-						new Toast(res.message);
-					}
-				});
-			}
-		}
-	}
-};
-</script>
-
-<style lang="less" scoped>
-.night-mode {
-	.tabs-container .tab-selection .button {
-		background: var(--dark-grey) !important;
-		color: var(--white) !important;
-	}
-}
-
-.songs {
-	.tabs-container {
-		.tab-selection {
-			display: flex;
-			overflow-x: auto;
-			.button {
-				border-radius: 0;
-				border: 0;
-				text-transform: uppercase;
-				font-size: 14px;
-				color: var(--dark-grey-3);
-				background-color: var(--light-grey-2);
-				flex-grow: 1;
-				height: 32px;
-
-				&:not(:first-of-type) {
-					margin-left: 5px;
-				}
-			}
-
-			.selected {
-				background-color: var(--primary-color) !important;
-				color: var(--white) !important;
-				font-weight: 600;
-			}
-		}
-		.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;
-			}
-		}
-	}
-
-	.musare-songs,
-	.universal-item:not(:last-of-type) {
-		margin-bottom: 10px;
-	}
-	.load-more-button {
-		width: 100%;
-		margin-top: 10px;
-	}
-
-	#playlist-info-section {
-		border: 1px solid var(--light-grey-3);
-		border-radius: @border-radius;
-		padding: 15px !important;
-		margin-bottom: 16px;
-
-		h3 {
-			font-weight: 600;
-			font-size: 30px;
-		}
-
-		h5 {
-			font-size: 18px;
-		}
-
-		h3,
-		h5 {
-			margin: 0;
-		}
-	}
-}
-</style>

+ 35 - 22
frontend/src/components/modals/ManageStation/index.vue

@@ -105,22 +105,31 @@
 								Settings
 								Settings
 							</button>
 							</button>
 							<button
 							<button
-								v-if="isAllowedToParty() || isOwnerOrAdmin()"
+								v-if="isOwnerOrAdmin()"
 								class="button is-default"
 								class="button is-default"
-								:class="{ selected: tab === 'playlists' }"
-								ref="playlists-tab"
-								@click="showTab('playlists')"
+								:class="{ selected: tab === 'autofill' }"
+								ref="autofill-tab"
+								@click="showTab('autofill')"
 							>
 							>
-								Playlists
+								Autofill
 							</button>
 							</button>
 							<button
 							<button
 								v-if="isAllowedToParty() || isOwnerOrAdmin()"
 								v-if="isAllowedToParty() || isOwnerOrAdmin()"
 								class="button is-default"
 								class="button is-default"
-								:class="{ selected: tab === 'songs' }"
-								ref="songs-tab"
-								@click="showTab('songs')"
+								:class="{ selected: tab === 'request' }"
+								ref="request-tab"
+								@click="showTab('request')"
+							>
+								Request
+							</button>
+							<button
+								v-if="isOwnerOrAdmin()"
+								class="button is-default"
+								:class="{ selected: tab === 'blacklist' }"
+								ref="blacklist-tab"
+								@click="showTab('blacklist')"
 							>
 							>
-								Songs
+								Blacklist
 							</button>
 							</button>
 						</div>
 						</div>
 						<settings
 						<settings
@@ -128,15 +137,20 @@
 							class="tab"
 							class="tab"
 							v-show="tab === 'settings'"
 							v-show="tab === 'settings'"
 						/>
 						/>
-						<playlists
-							v-if="isAllowedToParty() || isOwnerOrAdmin()"
+						<autofill
+							v-if="isOwnerOrAdmin()"
 							class="tab"
 							class="tab"
-							v-show="tab === 'playlists'"
+							v-show="tab === 'autofill'"
 						/>
 						/>
-						<songs
+						<request
 							v-if="isAllowedToParty() || isOwnerOrAdmin()"
 							v-if="isAllowedToParty() || isOwnerOrAdmin()"
 							class="tab"
 							class="tab"
-							v-show="tab === 'songs'"
+							v-show="tab === 'request'"
+						/>
+						<blacklist
+							v-if="isOwnerOrAdmin()"
+							class="tab"
+							v-show="tab === 'blacklist'"
 						/>
 						/>
 					</div>
 					</div>
 				</div>
 				</div>
@@ -150,10 +164,7 @@
 					<song-item
 					<song-item
 						v-if="currentSong._id"
 						v-if="currentSong._id"
 						:song="currentSong"
 						:song="currentSong"
-						:requested-by="
-							station.type === 'community' &&
-							station.partyMode === true
-						"
+						:requested-by="true"
 						header="Currently Playing.."
 						header="Currently Playing.."
 						class="currently-playing"
 						class="currently-playing"
 					/>
 					/>
@@ -187,8 +198,9 @@ import SongItem from "@/components/SongItem.vue";
 import Modal from "../../Modal.vue";
 import Modal from "../../Modal.vue";
 
 
 import Settings from "./Tabs/Settings.vue";
 import Settings from "./Tabs/Settings.vue";
-import Playlists from "./Tabs/Playlists.vue";
-import Songs from "./Tabs/Songs.vue";
+import Autofill from "./Tabs/Autofill.vue";
+import Request from "./Tabs/Request.vue";
+import Blacklist from "./Tabs/Blacklist.vue";
 
 
 export default {
 export default {
 	components: {
 	components: {
@@ -197,8 +209,9 @@ export default {
 		Queue,
 		Queue,
 		SongItem,
 		SongItem,
 		Settings,
 		Settings,
-		Playlists,
-		Songs
+		Autofill,
+		Request,
+		Blacklist
 	},
 	},
 	props: {
 	props: {
 		stationId: { type: String, default: "" },
 		stationId: { type: String, default: "" },

+ 0 - 37
frontend/src/pages/Home.vue

@@ -233,20 +233,6 @@
 								<span v-else class="songTitle"
 								<span v-else class="songTitle"
 									>No Songs Playing</span
 									>No Songs Playing</span
 								>
 								>
-								<i
-									class="material-icons stationMode"
-									:content="
-										element.partyMode
-											? 'Station in Party mode'
-											: 'Station in Playlist mode'
-									"
-									v-tippy="{ theme: 'info' }"
-									>{{
-										element.partyMode
-											? "emoji_people"
-											: "playlist_play"
-									}}</i
-								>
 							</div>
 							</div>
 						</router-link>
 						</router-link>
 					</template>
 					</template>
@@ -461,20 +447,6 @@
 							}}</span
 							}}</span
 						>
 						>
 						<span v-else class="songTitle">No Songs Playing</span>
 						<span v-else class="songTitle">No Songs Playing</span>
-						<i
-							class="material-icons stationMode"
-							:content="
-								station.partyMode
-									? 'Station in Party mode'
-									: 'Station in Playlist mode'
-							"
-							v-tippy="{ theme: 'info' }"
-							>{{
-								station.partyMode
-									? "emoji_people"
-									: "playlist_play"
-							}}</i
-						>
 					</div>
 					</div>
 				</router-link>
 				</router-link>
 				<h4 v-if="stations.length === 0">
 				<h4 v-if="stations.length === 0">
@@ -704,15 +676,6 @@ export default {
 			if (station) station.theme = theme;
 			if (station) station.theme = theme;
 		});
 		});
 
 
-		this.socket.on("event:station.partyMode.updated", res => {
-			const { stationId, partyMode } = res.data;
-			const station = this.stations.find(
-				station => station._id === stationId
-			);
-
-			if (station) station.partyMode = partyMode;
-		});
-
 		this.socket.on("event:station.nextSong", res => {
 		this.socket.on("event:station.nextSong", res => {
 			const station = this.stations.find(
 			const station = this.stations.find(
 				station => station._id === res.data.stationId
 				station => station._id === res.data.stationId

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

@@ -103,20 +103,6 @@
 											>star_border</i
 											>star_border</i
 										>
 										>
 									</a>
 									</a>
-									<i
-										class="material-icons stationMode"
-										:content="
-											station.partyMode
-												? 'Station in Party mode'
-												: 'Station in Playlist mode'
-										"
-										v-tippy="{ theme: 'info' }"
-										>{{
-											station.partyMode
-												? "emoji_people"
-												: "playlist_play"
-										}}</i
-									>
 								</div>
 								</div>
 								<p>{{ station.description }}</p>
 								<p>{{ station.description }}</p>
 							</div>
 							</div>
@@ -624,10 +610,7 @@
 								<song-item
 								<song-item
 									:song="currentSong"
 									:song="currentSong"
 									:duration="false"
 									:duration="false"
-									:requested-by="
-										station.type === 'community' &&
-										station.partyMode === true
-									"
+									:requested-by="true"
 									header="Currently Playing.."
 									header="Currently Playing.."
 								/>
 								/>
 							</div>
 							</div>
@@ -639,10 +622,7 @@
 								<song-item
 								<song-item
 									:song="nextSong"
 									:song="nextSong"
 									:duration="false"
 									:duration="false"
-									:requested-by="
-										station.type === 'community' &&
-										station.partyMode === true
-									"
+									:requested-by="true"
 									header="Next Up.."
 									header="Next Up.."
 								/>
 								/>
 							</div>
 							</div>
@@ -1152,11 +1132,6 @@ export default {
 				this.station.privatePlaylist = null;
 				this.station.privatePlaylist = null;
 		});
 		});
 
 
-		this.socket.on("event:station.partyMode.updated", res => {
-			if (this.station.type === "community")
-				this.station.partyMode = res.data.partyMode;
-		});
-
 		this.socket.on("event:station.theme.updated", res => {
 		this.socket.on("event:station.theme.updated", res => {
 			const { theme } = res.data;
 			const { theme } = res.data;
 			this.station.theme = theme;
 			this.station.theme = theme;
@@ -1909,8 +1884,6 @@ export default {
 		addPartyPlaylistSongToQueue() {
 		addPartyPlaylistSongToQueue() {
 			if (
 			if (
 				!this.partyPlaylistLock &&
 				!this.partyPlaylistLock &&
-				this.station.type === "community" &&
-				this.station.partyMode === true &&
 				this.songsList.length < 50 &&
 				this.songsList.length < 50 &&
 				this.currentUserQueueSongs < 3 &&
 				this.currentUserQueueSongs < 3 &&
 				this.partyPlaylists.length > 0
 				this.partyPlaylists.length > 0