فهرست منبع

refactor: Continued adapting station party mode into requests "module"

Owen Diffey 2 سال پیش
والد
کامیت
e13514ab10

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

@@ -321,7 +321,7 @@ CacheModule.runJob("SUB", {
 
 		stationModel.findOne(
 			{ _id: stationId },
-			["_id", "name", "displayName", "description", "type", "privacy", "owner", "partyMode", "playMode", "theme"],
+			["_id", "name", "displayName", "description", "type", "privacy", "owner", "requests", "playMode", "theme"],
 			(err, station) => {
 				WSModule.runJob("EMIT_TO_ROOMS", {
 					rooms: [`station.${stationId}`, `manage-station.${stationId}`, "admin.stations"],
@@ -818,7 +818,7 @@ export default {
 						name: station.name,
 						privacy: station.privacy,
 						locked: station.locked,
-						partyMode: station.partyMode,
+						requests: station.requests,
 						playMode: station.playMode,
 						owner: station.owner,
 						includedPlaylists: station.includedPlaylists,
@@ -946,7 +946,7 @@ export default {
 						name: station.name,
 						privacy: station.privacy,
 						locked: station.locked,
-						partyMode: station.partyMode,
+						requests: station.requests,
 						playMode: station.playMode,
 						owner: station.owner,
 						theme: station.theme,
@@ -1709,7 +1709,6 @@ export default {
 								privacy: "private",
 								queue: [],
 								currentSong: null,
-								partyMode: false,
 								playMode: "random"
 							},
 							next
@@ -1727,7 +1726,6 @@ export default {
 								owner: session.userId,
 								queue: [],
 								currentSong: null,
-								partyMode: true,
 								playMode: "random"
 							},
 							next

+ 3 - 1
backend/logic/db/schemas/station.js

@@ -41,7 +41,9 @@ export default {
 		}
 	],
 	owner: { type: String },
-	partyMode: { type: Boolean },
+	requests: {
+		enabled: { type: Boolean, default: true }
+	},
 	playMode: { type: String, enum: ["random", "sequential"], default: "random" },
 	theme: { type: String, enum: ["blue", "purple", "teal", "orange", "red"], default: "blue" },
 	includedPlaylists: [{ type: String }],

+ 8 - 0
backend/logic/migration/migrations/migration20.js

@@ -45,6 +45,14 @@ export default async function migrate(MigrationModule) {
 							);
 						}
 					});
+					stationModel.updateMany(
+						{ documentVersion: 7 },
+						{ $set: { "requests.enabled": true } },
+						(err, res) => {
+							this.log("INFO", `Migration 20. Stations found: ${res.modifiedCount}.`);
+							next(err);
+						}
+					);
 				}
 			],
 			err => {

+ 0 - 41
frontend/src/components/Queue.vue

@@ -69,46 +69,6 @@
 		<p class="nothing-here-text" v-else>
 			There are no songs currently queued
 		</p>
-		<button
-			class="button is-primary tab-actionable-button"
-			v-if="
-				sector === 'station' &&
-				loggedIn &&
-				((station.locked && isOwnerOnly()) ||
-					!station.locked ||
-					(station.locked && isAdminOnly() && dismissedWarning))
-			"
-			@click="
-				openModal('manageStation') & showManageStationTab('request')
-			"
-		>
-			<i class="material-icons icon-with-button">queue</i>
-			<span> Add Song To Queue </span>
-		</button>
-		<button
-			class="button is-primary tab-actionable-button disabled"
-			v-if="sector === 'station' && !loggedIn && !station.locked"
-			content="Login to add songs to queue"
-			v-tippy="{ theme: 'info' }"
-		>
-			<i class="material-icons icon-with-button">queue</i>
-			<span> Add Song To Queue </span>
-		</button>
-		<div id="queue-locked" v-if="station.locked">
-			<button
-				v-if="isAdminOnly() && !isOwnerOnly() && !dismissedWarning"
-				class="button tab-actionable-button"
-				@click="dismissedWarning = true"
-			>
-				THIS STATION'S QUEUE IS LOCKED.
-			</button>
-			<button
-				v-if="!isAdminOnly() && !isOwnerOnly()"
-				class="button tab-actionable-button"
-			>
-				THIS STATION'S QUEUE IS LOCKED.
-			</button>
-		</div>
 	</div>
 </template>
 
@@ -130,7 +90,6 @@ export default {
 	},
 	data() {
 		return {
-			dismissedWarning: false,
 			actionableButtonVisible: false,
 			drag: false
 		};

+ 62 - 67
frontend/src/components/modals/ManageStation/Tabs/Request.vue → frontend/src/components/Request.vue

@@ -11,6 +11,7 @@
 					Songs
 				</button>
 				<button
+					v-if="sector === 'station'"
 					class="button is-default"
 					ref="autorequest-tab"
 					:class="{ selected: tab === 'autorequest' }"
@@ -18,6 +19,14 @@
 				>
 					Autorequest
 				</button>
+				<button
+					v-else
+					class="button is-default disabled"
+					content="Only available on station pages"
+					v-tippy
+				>
+					Autorequest
+				</button>
 			</div>
 			<div
 				class="tab"
@@ -162,7 +171,7 @@
 					</div>
 				</div>
 			</div>
-			<div v-show="tab === 'autorequest'">
+			<div v-if="sector === 'station'" v-show="tab === 'autorequest'">
 				<div class="tab-selection">
 					<button
 						class="button is-default"
@@ -238,11 +247,7 @@
 								>
 								<quick-confirm
 									v-if="isSelected(featuredPlaylist._id)"
-									@confirm="
-										deselectPartyPlaylist(
-											featuredPlaylist._id
-										)
-									"
+									@confirm="deselect(featuredPlaylist._id)"
 								>
 									<i
 										class="material-icons stop-icon"
@@ -257,9 +262,7 @@
 										!isSelected(featuredPlaylist._id) &&
 										!isBlacklisted(featuredPlaylist._id)
 									"
-									@click="
-										selectPartyPlaylist(featuredPlaylist)
-									"
+									@click="select(featuredPlaylist)"
 									class="material-icons play-icon"
 									content="Request songs from this playlist"
 									v-tippy
@@ -393,9 +396,7 @@
 								>
 								<quick-confirm
 									v-if="isSelected(playlist._id)"
-									@confirm="
-										deselectPartyPlaylist(playlist._id)
-									"
+									@confirm="deselect(playlist._id)"
 								>
 									<i
 										class="material-icons stop-icon"
@@ -410,7 +411,7 @@
 										!isSelected(playlist._id) &&
 										!isBlacklisted(playlist._id)
 									"
-									@click="selectPartyPlaylist(playlist)"
+									@click="select(playlist)"
 									class="material-icons play-icon"
 									content="Request songs from this playlist"
 									v-tippy
@@ -544,9 +545,7 @@
 									<template #actions>
 										<i
 											v-if="!isSelected(element._id)"
-											@click="
-												selectPartyPlaylist(element)
-											"
+											@click="select(element)"
 											class="material-icons play-icon"
 											content="Request songs from this playlist"
 											v-tippy
@@ -554,11 +553,7 @@
 										>
 										<quick-confirm
 											v-if="isSelected(element._id)"
-											@confirm="
-												deselectPartyPlaylist(
-													element._id
-												)
-											"
+											@confirm="deselect(element._id)"
 										>
 											<i
 												class="material-icons stop-icon"
@@ -620,9 +615,9 @@
 					</p>
 				</div>
 				<div class="tab" v-show="childTab === 'current'">
-					<div v-if="partyPlaylists.length > 0">
+					<div v-if="autoRequest.length > 0">
 						<playlist-item
-							v-for="playlist in partyPlaylists"
+							v-for="playlist in autoRequest"
 							:key="`key-${playlist._id}`"
 							:playlist="playlist"
 							:show-owner="true"
@@ -640,9 +635,7 @@
 							<template #actions>
 								<quick-confirm
 									v-if="isOwnerOrAdmin()"
-									@confirm="
-										deselectPartyPlaylist(playlist._id)
-									"
+									@confirm="deselect(playlist._id)"
 								>
 									<i
 										class="material-icons stop-icon"
@@ -703,7 +696,7 @@ 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 SearchQueryItem from "@/components/SearchQueryItem.vue";
 
 import SortablePlaylists from "@/mixins/SortablePlaylists.vue";
 import SearchYoutube from "@/mixins/SearchYoutube.vue";
@@ -717,6 +710,9 @@ export default {
 		SearchQueryItem
 	},
 	mixins: [SortablePlaylists, SearchYoutube, SearchMusare],
+	props: {
+		sector: { type: String, default: "station" }
+	},
 	data() {
 		return {
 			tab: "songs",
@@ -751,19 +747,22 @@ export default {
 					.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
+		currentUserQueueSongs() {
+			return this.songsList.filter(
+				queueSong => queueSong.requestedBy === this.userId
+			).length;
+		},
+		...mapState("user", {
+			loggedIn: state => state.auth.loggedIn,
+			role: state => state.auth.role,
+			userId: state => state.auth.userId
 		}),
-		...mapState("modals/manageStation", {
-			parentTab: state => state.tab,
-			originalStation: state => state.originalStation,
+		...mapState("station", {
 			station: state => state.station,
-			includedPlaylists: state => state.includedPlaylists,
-			blacklist: state => state.blacklist,
-			songsList: state => state.songsList
+			songsList: state => state.songsList,
+			autoRequest: state => state.autoRequest,
+			autoRequestLock: state => state.autoRequestLock,
+			blacklist: state => state.blacklist
 		}),
 		...mapGetters({
 			socket: "websockets/getSocket"
@@ -773,6 +772,10 @@ export default {
 		this.showTab("songs");
 
 		ws.onConnect(this.init);
+
+		this.socket.on("event:station.queue.updated", () =>
+			this.autoRequestSong()
+		);
 	},
 	methods: {
 		init() {
@@ -787,25 +790,12 @@ export default {
 					this.featuredPlaylists = res.data.playlists;
 			});
 
-			this.socket.dispatch(
-				`stations.getStationIncludedPlaylistsById`,
-				this.station._id,
-				res => {
-					if (res.status === "success") {
-						this.station.includedPlaylists = res.data.playlists;
-						this.originalStation.includedPlaylists =
-							res.data.playlists;
-					}
-				}
-			);
-
 			this.socket.dispatch(
 				`stations.getStationBlacklistById`,
 				this.station._id,
 				res => {
 					if (res.status === "success") {
 						this.station.blacklist = res.data.playlists;
-						this.originalStation.blacklist = res.data.playlists;
 					}
 				}
 			);
@@ -835,10 +825,10 @@ export default {
 			this.editPlaylist(playlistId);
 			this.openModal("editPlaylist");
 		},
-		selectPartyPlaylist(playlist) {
+		select(playlist) {
 			if (!this.isSelected(playlist.id)) {
-				this.partyPlaylists.push(playlist);
-				this.addPartyPlaylistSongToQueue();
+				this.autoRequest.push(playlist);
+				this.autoRequestSong();
 				new Toast(
 					"Successfully selected playlist to auto request songs."
 				);
@@ -846,13 +836,13 @@ export default {
 				new Toast("Error: Playlist already selected.");
 			}
 		},
-		deselectPartyPlaylist(id) {
+		deselect(id) {
 			return new Promise(resolve => {
 				let selected = false;
-				this.partyPlaylists.forEach((playlist, index) => {
+				this.autoRequest.forEach((playlist, index) => {
 					if (playlist._id === id) {
 						selected = true;
-						this.partyPlaylists.splice(index, 1);
+						this.autoRequest.splice(index, 1);
 					}
 				});
 				if (selected) {
@@ -879,7 +869,7 @@ export default {
 		},
 		isSelected(id) {
 			let selected = false;
-			this.partyPlaylists.forEach(playlist => {
+			this.autoRequest.forEach(playlist => {
 				if (playlist._id === id) selected = true;
 			});
 			return selected;
@@ -945,17 +935,16 @@ export default {
 				}
 			);
 		},
-		addPartyPlaylistSongToQueue() {
+		autoRequestSong() {
 			if (
+				!this.autoRequestLock &&
 				this.songsList.length < 50 &&
-				this.songsList.filter(
-					queueSong => queueSong.requestedBy === this.userId
-				).length < 3 &&
-				this.partyPlaylists
+				this.currentUserQueueSongs < 3 &&
+				this.autoRequest.length > 0
 			) {
 				const selectedPlaylist =
-					this.partyPlaylists[
-						Math.floor(Math.random() * this.partyPlaylists.length)
+					this.autoRequest[
+						Math.floor(Math.random() * this.autoRequest.length)
 					];
 				if (selectedPlaylist._id && selectedPlaylist.songs.length > 0) {
 					const selectedSong =
@@ -965,13 +954,15 @@ export default {
 							)
 						];
 					if (selectedSong.youtubeId) {
+						this.autoRequestLock = true;
 						this.socket.dispatch(
 							"stations.addToQueue",
 							this.station._id,
 							selectedSong.youtubeId,
 							data => {
+								this.autoRequestLock = false;
 								if (data.status !== "success")
-									this.addPartyPlaylistSongToQueue();
+									this.autoRequestSong();
 							}
 						);
 					}
@@ -997,7 +988,7 @@ export default {
 				}
 			);
 		},
-		...mapActions("station", ["updatePartyPlaylists"]),
+		...mapActions("station", ["updateAutoRequest"]),
 		...mapActions("modalVisibility", ["openModal"]),
 		...mapActions("user/playlists", ["editPlaylist", "setPlaylists"])
 	}
@@ -1024,6 +1015,10 @@ export default {
 	color: var(--purple);
 }
 
+#create-new-playlist-button {
+	width: 100%;
+}
+
 .station-playlists {
 	.tabs-container {
 		.tab-selection {

+ 1 - 4
frontend/src/components/modals/EditPlaylist/index.vue

@@ -114,10 +114,7 @@
 										<template #tippyActions>
 											<i
 												class="material-icons add-to-queue-icon"
-												v-if="
-													station.partyMode &&
-													!station.locked
-												"
+												v-if="!station.locked"
 												@click="
 													addSongToQueue(
 														element.youtubeId

+ 1 - 104
frontend/src/components/modals/ManageStation/Tabs/Autofill.vue

@@ -540,8 +540,7 @@ export default {
 		...mapState({
 			loggedIn: state => state.user.auth.loggedIn,
 			role: state => state.user.auth.role,
-			userId: state => state.user.auth.userId,
-			partyPlaylists: state => state.station.partyPlaylists
+			userId: state => state.user.auth.userId
 		}),
 		...mapState("modals/manageStation", {
 			parentTab: state => state.tab,
@@ -555,18 +554,6 @@ export default {
 			socket: "websockets/getSocket"
 		})
 	},
-	watch: {
-		// eslint-disable-next-line func-names
-		parentTab(value) {
-			if (value === "playlists") {
-				if (this.tab === "included" && this.isPartyMode()) {
-					this.showTab("party");
-				} else if (this.tab === "party" && this.isPlaylistMode()) {
-					this.showTab("included");
-				}
-			}
-		}
-	},
 	mounted() {
 		this.showTab("search");
 
@@ -625,39 +612,10 @@ export default {
 		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();
-		},
 		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.");
-			}
-		},
 		includePlaylist(playlist) {
 			this.socket.dispatch(
 				"stations.includePlaylist",
@@ -668,24 +626,6 @@ export default {
 				}
 			);
 		},
-		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();
-				}
-			});
-		},
 		removeIncludedPlaylist(id) {
 			return new Promise(resolve => {
 				this.socket.dispatch(
@@ -712,13 +652,6 @@ export default {
 				);
 			});
 		},
-		isSelected(id) {
-			let selected = false;
-			this.partyPlaylists.forEach(playlist => {
-				if (playlist._id === id) selected = true;
-			});
-			return selected;
-		},
 		isIncluded(id) {
 			let included = false;
 			this.includedPlaylists.forEach(playlist => {
@@ -787,42 +720,6 @@ export default {
 				}
 			);
 		},
-		addPartyPlaylistSongToQueue() {
-			if (
-				this.station.type === "community" &&
-				this.station.partyMode === true &&
-				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();
-							}
-						);
-					}
-				}
-			}
-		},
-		...mapActions("station", ["updatePartyPlaylists"]),
 		...mapActions("modalVisibility", ["openModal"]),
 		...mapActions("user/playlists", ["editPlaylist", "setPlaylists"])
 	}

+ 1 - 2
frontend/src/components/modals/ManageStation/Tabs/Blacklist.vue

@@ -78,8 +78,7 @@ export default {
 		...mapState({
 			loggedIn: state => state.user.auth.loggedIn,
 			role: state => state.user.auth.role,
-			userId: state => state.user.auth.userId,
-			partyPlaylists: state => state.station.partyPlaylists
+			userId: state => state.user.auth.userId
 		}),
 		...mapState("modals/manageStation", {
 			originalStation: state => state.originalStation,

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

@@ -158,7 +158,7 @@
 			</div>
 		</div>
 
-		<hr />
+		<hr class="section-horizontal-rule" />
 
 		<button class="control is-expanded button is-primary" @click="update()">
 			Save Changes
@@ -207,7 +207,7 @@ export default {
 		this.local = {
 			...this.station,
 			requests: {
-				enabled: true,
+				enabled: this.station.requests.enabled,
 				access: "owner"
 			},
 			autofill: {

+ 12 - 31
frontend/src/components/modals/ManageStation/index.vue

@@ -4,7 +4,7 @@
 		:title="
 			sector === 'home' && !isOwnerOrAdmin()
 				? 'View Queue'
-				: !isOwnerOrAdmin() && station.partyMode
+				: !isOwnerOrAdmin()
 				? 'Add Song to Queue'
 				: 'Manage Station'
 		"
@@ -28,20 +28,6 @@
 								>
 									check_circle
 								</i>
-								<i
-									class="material-icons stationMode"
-									:content="
-										station.partyMode
-											? 'Station in Party mode'
-											: 'Station in Playlist mode'
-									"
-									v-tippy
-									>{{
-										station.partyMode
-											? "emoji_people"
-											: "playlist_play"
-									}}</i
-								>
 							</div>
 							<p>{{ station.description }}</p>
 						</div>
@@ -114,7 +100,10 @@
 								Autofill
 							</button>
 							<button
-								v-if="isAllowedToParty() || isOwnerOrAdmin()"
+								v-if="
+									station.requests.enabled &&
+									(isAllowedToParty() || isOwnerOrAdmin())
+								"
 								class="button is-default"
 								:class="{ selected: tab === 'request' }"
 								ref="request-tab"
@@ -143,9 +132,13 @@
 							v-show="tab === 'autofill'"
 						/>
 						<request
-							v-if="isAllowedToParty() || isOwnerOrAdmin()"
+							v-if="
+								station.requests.enabled &&
+								(isAllowedToParty() || isOwnerOrAdmin())
+							"
 							class="tab"
 							v-show="tab === 'request'"
+							:sector="sector"
 						/>
 						<blacklist
 							v-if="isOwnerOrAdmin()"
@@ -199,7 +192,7 @@ import Modal from "../../Modal.vue";
 
 import Settings from "./Tabs/Settings.vue";
 import Autofill from "./Tabs/Autofill.vue";
-import Request from "./Tabs/Request.vue";
+import Request from "@/components/Request.vue";
 import Blacklist from "./Tabs/Blacklist.vue";
 
 export default {
@@ -244,8 +237,7 @@ export default {
 				const { station } = res.data;
 				this.editStation(station);
 
-				if (!this.isOwnerOrAdmin() && this.station.partyMode)
-					this.showTab("songs");
+				if (!this.isOwnerOrAdmin()) this.showTab("songs");
 
 				const currentSong = res.data.station.currentSong
 					? res.data.station.currentSong
@@ -509,24 +501,13 @@ export default {
 		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();
-		},
 		removeStation() {
 			this.socket.dispatch("stations.remove", this.station._id, res => {
 				new Toast(res.message);

+ 0 - 26
frontend/src/pages/Admin/Stations.vue

@@ -84,14 +84,6 @@
 						:link="true"
 					/>
 				</template>
-				<template #column-stationMode="slotProps">
-					<span
-						:title="slotProps.item.partyMode ? 'Party' : 'Playlist'"
-						>{{
-							slotProps.item.partyMode ? "Party" : "Playlist"
-						}}</span
-					>
-				</template>
 				<template #column-playMode="slotProps">
 					<span :title="slotProps.item.playMode">{{
 						slotProps.item.playMode === "random"
@@ -225,13 +217,6 @@ export default {
 					sortProperty: "owner",
 					defaultWidth: 150
 				},
-				{
-					name: "stationMode",
-					displayName: "Station Mode",
-					properties: ["partyMode"],
-					sortable: false,
-					defaultVisibility: "hidden"
-				},
 				{
 					name: "playMode",
 					displayName: "Play Mode",
@@ -306,17 +291,6 @@ export default {
 					filterTypes: ["contains", "exact", "regex"],
 					defaultFilterType: "contains"
 				},
-				{
-					name: "stationMode",
-					displayName: "Station Mode",
-					property: "partyMode",
-					filterTypes: ["boolean"],
-					defaultFilterType: "boolean",
-					dropdown: [
-						[true, "Party"],
-						[false, "Playlist"]
-					]
-				},
 				{
 					name: "playMode",
 					displayName: "Play Mode",

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

@@ -26,35 +26,27 @@
 							<i
 								v-if="
 									station.type === 'community' &&
-									(isOwnerOrAdmin() || station.partyMode) &&
+									isOwnerOrAdmin() &&
 									!isSelected(element._id) &&
 									!isBlacklisted(element._id)
 								"
 								@click="selectPlaylist(element)"
 								class="material-icons play-icon"
-								:content="
-									station.partyMode
-										? 'Request songs from this playlist'
-										: 'Play songs from this playlist'
-								"
+								content="Request songs from this playlist"
 								v-tippy
 								>play_arrow</i
 							>
 							<quick-confirm
 								v-if="
 									station.type === 'community' &&
-									(isOwnerOrAdmin() || station.partyMode) &&
+									isOwnerOrAdmin() &&
 									isSelected(element._id)
 								"
 								@confirm="deselectPlaylist(element._id)"
 							>
 								<i
 									class="material-icons stop-icon"
-									:content="
-										station.partyMode
-											? 'Stop requesting songs from this playlist'
-											: 'Stop playing songs from this playlist'
-									"
+									content="Stop requesting songs from this playlist"
 									v-tippy
 									>stop</i
 								>
@@ -114,8 +106,7 @@ export default {
 	mixins: [SortablePlaylists],
 	computed: {
 		currentPlaylists() {
-			if (this.station.type === "community" && this.station.partyMode)
-				return this.partyPlaylists;
+			if (this.station.type === "community") return this.autoRequest;
 
 			return this.includedPlaylists;
 		},
@@ -125,7 +116,7 @@ export default {
 			loggedIn: state => state.user.auth.loggedIn
 		}),
 		...mapState("station", {
-			partyPlaylists: state => state.partyPlaylists,
+			autoRequest: state => state.autoRequest,
 			includedPlaylists: state => state.includedPlaylists,
 			blacklist: state => state.blacklist,
 			songsList: state => state.songsList
@@ -193,10 +184,9 @@ export default {
 			this.openModal("editPlaylist");
 		},
 		selectPlaylist(playlist) {
-			if (this.station.type === "community" && this.station.partyMode) {
+			if (this.station.type === "community") {
 				if (!this.isSelected(playlist.id)) {
-					this.partyPlaylists.push(playlist);
-					this.addPartyPlaylistSongToQueue();
+					this.autoRequest.push(playlist);
 					new Toast(
 						"Successfully selected playlist to auto request songs."
 					);
@@ -216,15 +206,12 @@ export default {
 		},
 		deselectPlaylist(id) {
 			return new Promise(resolve => {
-				if (
-					this.station.type === "community" &&
-					this.station.partyMode
-				) {
+				if (this.station.type === "community") {
 					let selected = false;
 					this.currentPlaylists.forEach((playlist, index) => {
 						if (playlist._id === id) {
 							selected = true;
-							this.partyPlaylists.splice(index, 1);
+							this.autoRequest.splice(index, 1);
 						}
 					});
 					if (selected) {
@@ -273,42 +260,6 @@ export default {
 				}
 			);
 		},
-		addPartyPlaylistSongToQueue() {
-			if (
-				this.station.type === "community" &&
-				this.station.partyMode === true &&
-				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();
-							}
-						);
-					}
-				}
-			}
-		},
-		...mapActions("station", ["updatePartyPlaylists"]),
 		...mapActions("modalVisibility", ["openModal"]),
 		...mapActions("user/playlists", ["editPlaylist", "setPlaylists"])
 	}

+ 31 - 11
frontend/src/pages/Station/Sidebar/index.vue

@@ -16,25 +16,30 @@
 				Users
 			</button>
 			<button
-				v-if="loggedIn"
+				v-if="station.requests && station.requests.enabled && loggedIn"
 				class="button is-default"
-				:class="{ selected: tab === 'playlists' }"
-				@click="showTab('playlists')"
+				:class="{ selected: tab === 'requests' }"
+				@click="showTab('requests')"
 			>
-				My Playlists
+				Requests
 			</button>
 			<button
-				v-else
+				v-else-if="station.requests && station.requests.enabled"
 				class="button is-default"
-				content="Login to manage playlists"
+				content="Login to request songs"
 				v-tippy="{ theme: 'info' }"
 			>
-				My Playlists
+				Requests
 			</button>
 		</div>
 		<queue class="tab" v-show="tab === 'queue'" />
 		<users class="tab" v-show="tab === 'users'" />
-		<playlists class="tab" v-show="tab === 'playlists'" />
+		<request
+			v-if="station.requests && station.requests.enabled && loggedIn"
+			v-show="tab === 'requests'"
+			class="tab requests-tab"
+			sector="station"
+		/>
 	</div>
 </template>
 
@@ -44,10 +49,10 @@ import { mapActions, mapState } from "vuex";
 import Queue from "@/components/Queue.vue";
 import TabQueryHandler from "@/mixins/TabQueryHandler.vue";
 import Users from "./Users.vue";
-import Playlists from "./Playlists.vue";
+import Request from "@/components/Request.vue";
 
 export default {
-	components: { Queue, Users, Playlists },
+	components: { Queue, Users, Request },
 	mixins: [TabQueryHandler],
 	data() {
 		return {
@@ -55,6 +60,7 @@ export default {
 		};
 	},
 	computed: mapState({
+		station: state => state.station.station,
 		users: state => state.station.users,
 		userCount: state => state.station.userCount,
 		loggedIn: state => state.user.auth.loggedIn
@@ -63,7 +69,7 @@ export default {
 		if (
 			this.$route.query.tab === "queue" ||
 			this.$route.query.tab === "users" ||
-			this.$route.query.tab === "playlists"
+			this.$route.query.tab === "requests"
 		)
 			this.tab = this.$route.query.tab;
 	},
@@ -79,6 +85,11 @@ export default {
 		background: var(--dark-grey);
 		color: var(--white);
 	}
+
+	.tab.requests-tab {
+		background-color: var(--dark-grey-3) !important;
+		border: 0 !important;
+	}
 }
 
 #tabs-container .tab {
@@ -122,6 +133,15 @@ export default {
 	.nothing-here-text:not(:only-child) {
 		height: calc(100% - 40px);
 	}
+
+	&.requests-tab {
+		background-color: var(--white);
+		margin-bottom: 20px;
+		border-radius: 0 0 @border-radius @border-radius;
+		max-height: 100%;
+		padding: 15px;
+		overflow-y: auto;
+	}
 }
 
 :deep(.tab-actionable-button) {

+ 7 - 43
frontend/src/pages/Station/index.vue

@@ -678,7 +678,8 @@
 				<span><b>Local paused</b>: {{ localPaused }}</span>
 				<span><b>Station paused</b>: {{ stationPaused }}</span>
 				<span
-					><b>Party playlists selected</b>: {{ partyPlaylists }}</span
+					><b>Auto requesting playlists</b>:
+					{{ autoRequest.join(", ") }}</span
 				>
 				<span><b>Skip votes loaded</b>: {{ skipVotesLoaded }}</span>
 				<span
@@ -845,7 +846,6 @@ export default {
 			socketConnected: null,
 			persistentToastCheckerInterval: null,
 			persistentToasts: [],
-			partyPlaylistLock: false,
 			mediasession: false,
 			christmas: false
 		};
@@ -897,7 +897,7 @@ export default {
 			stationPaused: state => state.stationPaused,
 			localPaused: state => state.localPaused,
 			noSong: state => state.noSong,
-			partyPlaylists: state => state.partyPlaylists,
+			autoRequest: state => state.autoRequest,
 			includedPlaylists: state => state.includedPlaylists,
 			blacklist: state => state.blacklist
 		}),
@@ -1098,8 +1098,6 @@ export default {
 					: null;
 
 			this.updateNextSong(nextSong);
-
-			this.addPartyPlaylistSongToQueue();
 		});
 
 		this.socket.on("event:station.queue.song.repositioned", res => {
@@ -1861,40 +1859,6 @@ export default {
 				}
 			);
 		},
-		addPartyPlaylistSongToQueue() {
-			if (
-				!this.partyPlaylistLock &&
-				this.songsList.length < 50 &&
-				this.currentUserQueueSongs < 3 &&
-				this.partyPlaylists.length > 0
-			) {
-				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.partyPlaylistLock = true;
-						this.socket.dispatch(
-							"stations.addToQueue",
-							this.station._id,
-							selectedSong.youtubeId,
-							data => {
-								this.partyPlaylistLock = false;
-								if (data.status !== "success")
-									this.addPartyPlaylistSongToQueue();
-							}
-						);
-					}
-				}
-			}
-		},
 		togglePlayerDebugBox() {
 			this.$refs.playerDebugBox.toggleBox();
 		},
@@ -1924,7 +1888,6 @@ export default {
 							description,
 							privacy,
 							locked,
-							partyMode,
 							owner,
 							privatePlaylist,
 							includedPlaylists,
@@ -1933,7 +1896,8 @@ export default {
 							genres,
 							blacklistedGenres,
 							isFavorited,
-							theme
+							theme,
+							requests
 						} = res.data;
 
 						// change url to use station name instead of station id
@@ -1949,7 +1913,6 @@ export default {
 							description,
 							privacy,
 							locked,
-							partyMode,
 							owner,
 							privatePlaylist,
 							includedPlaylists,
@@ -1958,7 +1921,8 @@ export default {
 							genres,
 							blacklistedGenres,
 							isFavorited,
-							theme
+							theme,
+							requests
 						});
 
 						document.getElementsByTagName(

+ 14 - 6
frontend/src/store/modules/station.js

@@ -2,7 +2,8 @@
 
 const state = {
 	station: {},
-	partyPlaylists: [],
+	autoRequest: [],
+	autoRequestLock: false,
 	editing: {},
 	userCount: 0,
 	users: {
@@ -61,8 +62,11 @@ const actions = {
 	updateNoSong: ({ commit }, noSong) => {
 		commit("updateNoSong", noSong);
 	},
-	updatePartyPlaylists: ({ commit }, playlists) => {
-		commit("updatePartyPlaylists", playlists);
+	updateAutoRequest: ({ commit }, playlists) => {
+		commit("updateAutoRequest", playlists);
+	},
+	updateAutoRequestLock: ({ commit }, lock) => {
+		commit("updateAutoRequestLock", lock);
 	},
 	updateIfStationIsFavorited: ({ commit }, { isFavorited }) => {
 		commit("updateIfStationIsFavorited", isFavorited);
@@ -93,7 +97,8 @@ const mutations = {
 	},
 	leaveStation(state) {
 		state.station = {};
-		state.partyPlaylists = [];
+		state.autoRequest = [];
+		state.autoRequestLock = false;
 		state.editing = {};
 		state.userCount = 0;
 		state.users = {
@@ -156,8 +161,11 @@ const mutations = {
 	updateNoSong(state, noSong) {
 		state.noSong = noSong;
 	},
-	updatePartyPlaylists(state, playlists) {
-		state.partyPlaylists = playlists;
+	updateAutoRequest(state, playlists) {
+		state.autoRequest = playlists;
+	},
+	updateAutoRequestLock(state, lock) {
+		state.autoRequestLock = lock;
 	},
 	updateIfStationIsFavorited(state, isFavorited) {
 		state.station.isFavorited = isFavorited;