Browse Source

Added Request Song modal for official stations

Owen Diffey 3 years ago
parent
commit
ad3bd36c48

+ 281 - 0
frontend/src/components/modals/RequestSong.vue

@@ -0,0 +1,281 @@
+<template>
+	<modal title="Request Song">
+		<div slot="body">
+			<div class="vertical-padding">
+				<!-- Choosing a song from youtube -->
+
+				<h4 class="section-title">Choose a song</h4>
+				<p class="section-description">
+					Choose a song by searching or using a link from YouTube.
+				</p>
+
+				<br />
+
+				<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="search.songs.query"
+							autofocus
+							@keyup.enter="searchForSongs()"
+						/>
+					</p>
+					<p class="control">
+						<a
+							class="button is-info"
+							@click.prevent="searchForSongs()"
+							href="#"
+							><i class="material-icons icon-with-button"
+								>search</i
+							>Search</a
+						>
+					</p>
+				</div>
+
+				<!-- Choosing a song from youtube - query results -->
+
+				<div
+					id="song-query-results"
+					v-if="search.songs.results.length > 0"
+				>
+					<search-query-item
+						v-for="(result, index) in search.songs.results"
+						:key="index"
+						:result="result"
+					>
+						<div slot="actions">
+							<transition
+								name="search-query-actions"
+								mode="out-in"
+							>
+								<a
+									class="button is-success"
+									v-if="result.isAddedToQueue"
+									href="#"
+									key="added-to-playlist"
+								>
+									<i class="material-icons icon-with-button"
+										>done</i
+									>
+									Added to queue
+								</a>
+								<a
+									class="button is-dark"
+									v-else
+									@click.prevent="
+										addSongToQueue(result.id, index)
+									"
+									href="#"
+									key="add-to-queue"
+								>
+									<i class="material-icons icon-with-button"
+										>add</i
+									>
+									Add to queue
+								</a>
+							</transition>
+						</div>
+					</search-query-item>
+
+					<a
+						class="button is-default load-more-button"
+						@click.prevent="loadMoreSongs()"
+						href="#"
+					>
+						Load more...
+					</a>
+				</div>
+
+				<!-- Import a playlist from youtube -->
+
+				<div v-if="station.type === 'official'">
+					<hr class="section-horizontal-rule" />
+
+					<h4 class="section-title">Import a playlist</h4>
+					<p class="section-description">
+						Import a playlist by using a link from YouTube.
+					</p>
+
+					<br />
+
+					<div class="control is-grouped input-with-button">
+						<p class="control is-expanded">
+							<input
+								class="input"
+								type="text"
+								placeholder="YouTube Playlist URL"
+								v-model="search.playlist.query"
+								@keyup.enter="importPlaylist()"
+							/>
+						</p>
+						<p class="control has-addons">
+							<span class="select" id="playlist-import-type">
+								<select
+									v-model="
+										search.playlist.isImportingOnlyMusic
+									"
+								>
+									<option :value="false">Import all</option>
+									<option :value="true">
+										Import only music
+									</option>
+								</select>
+							</span>
+							<a
+								class="button is-info"
+								@click.prevent="importPlaylist()"
+								href="#"
+								><i class="material-icons icon-with-button"
+									>publish</i
+								>Import</a
+							>
+						</p>
+					</div>
+				</div>
+			</div>
+		</div>
+	</modal>
+</template>
+
+<script>
+import { mapState, mapGetters } from "vuex";
+
+import Toast from "toasters";
+
+import SearchYoutube from "@/mixins/SearchYoutube.vue";
+
+import SearchQueryItem from "../SearchQueryItem.vue";
+import Modal from "../Modal.vue";
+
+export default {
+	components: { Modal, SearchQueryItem },
+	mixins: [SearchYoutube],
+	computed: {
+		...mapState({
+			loggedIn: state => state.user.auth.loggedIn,
+			station: state => state.station.station
+		}),
+		...mapGetters({
+			socket: "websockets/getSocket"
+		})
+	},
+	methods: {
+		addSongToQueue(songId, index) {
+			if (this.station.type === "community") {
+				this.socket.dispatch(
+					"stations.addToQueue",
+					this.station._id,
+					songId,
+					data => {
+						if (data.status !== "success")
+							new Toast(`Error: ${data.message}`);
+						else {
+							this.search.songs.results[
+								index
+							].isAddedToQueue = true;
+
+							new Toast(data.message);
+						}
+					}
+				);
+			} else {
+				this.socket.dispatch("songs.request", songId, data => {
+					if (data.status !== "success")
+						new Toast(`Error: ${data.message}`);
+					else {
+						this.search.songs.results[index].isAddedToQueue = true;
+
+						new Toast(data.message);
+					}
+				});
+			}
+		},
+		importPlaylist() {
+			let isImportingPlaylist = true;
+
+			// import query is blank
+			if (!this.search.playlist.query)
+				return new Toast("Please enter a YouTube playlist URL.");
+
+			const regex = new RegExp(`[\\?&]list=([^&#]*)`);
+			const splitQuery = regex.exec(this.search.playlist.query);
+
+			if (!splitQuery) {
+				return new Toast({
+					content: "Please enter a valid YouTube playlist URL.",
+					timeout: 4000
+				});
+			}
+
+			// don't give starting import message instantly in case of instant error
+			setTimeout(() => {
+				if (isImportingPlaylist) {
+					new Toast(
+						"Starting to import your playlist. This can take some time to do."
+					);
+				}
+			}, 750);
+
+			return this.socket.dispatch(
+				"songs.requestSet",
+				this.search.playlist.query,
+				this.search.playlist.isImportingOnlyMusic,
+				res => {
+					isImportingPlaylist = false;
+					return new Toast({ content: res.message, timeout: 20000 });
+				}
+			);
+		}
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+.night-mode {
+	div {
+		color: var(--dark-grey);
+	}
+}
+
+.song-actions {
+	.button {
+		height: 36px;
+		width: 140px;
+	}
+}
+
+.song-thumbnail div {
+	width: 96px;
+	height: 54px;
+	background-position: center;
+	background-repeat: no-repeat;
+}
+
+.table {
+	margin-bottom: 0;
+	margin-top: 20px;
+}
+
+.vertical-padding {
+	padding: 20px;
+}
+
+#song-query-results {
+	padding: 10px;
+	max-height: 500px;
+	overflow: auto;
+	border: 1px solid var(--light-grey-3);
+	border-radius: 3px;
+
+	.search-query-item:not(:last-of-type) {
+		margin-bottom: 10px;
+	}
+
+	.load-more-button {
+		width: 100%;
+		margin-top: 10px;
+	}
+}
+</style>

+ 18 - 8
frontend/src/pages/Station/Sidebar/Queue.vue

@@ -71,14 +71,11 @@
 			class="button is-primary tab-actionable-button"
 			v-if="
 				loggedIn &&
-					((station.type === 'community' &&
-						station.partyMode &&
-						((station.locked && isOwnerOnly()) ||
-							!station.locked ||
-							(station.locked &&
-								isAdminOnly() &&
-								dismissedWarning))) ||
-						station.type === 'official')
+					station.type === 'community' &&
+					station.partyMode &&
+					((station.locked && isOwnerOnly()) ||
+						!station.locked ||
+						(station.locked && isAdminOnly() && dismissedWarning))
 			"
 			@click="
 				openModal({
@@ -90,6 +87,19 @@
 			<i class="material-icons icon-with-button">queue</i>
 			<span class="optional-desktop-only-text"> Add Song To Queue </span>
 		</button>
+		<button
+			class="button is-primary tab-actionable-button"
+			v-if="loggedIn && station.type === 'official'"
+			@click="
+				openModal({
+					sector: 'station',
+					modal: 'requestSong'
+				})
+			"
+		>
+			<i class="material-icons icon-with-button">queue</i>
+			<span class="optional-desktop-only-text"> Request Song </span>
+		</button>
 		<button
 			class="button is-primary tab-actionable-button disabled"
 			v-if="

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

@@ -564,6 +564,7 @@
 				</div>
 
 				<song-queue v-if="modals.station.addSongToQueue" />
+				<request-song v-if="modals.station.requestSong" />
 				<edit-playlist v-if="modals.station.editPlaylist" />
 				<create-playlist v-if="modals.station.createPlaylist" />
 				<edit-station
@@ -658,6 +659,7 @@ export default {
 		MainHeader,
 		MainFooter,
 		SongQueue: () => import("@/components/modals/AddSongToQueue.vue"),
+		RequestSong: () => import("@/components/modals/RequestSong.vue"),
 		EditPlaylist: () => import("@/components/modals/EditPlaylist.vue"),
 		CreatePlaylist: () => import("@/components/modals/CreatePlaylist.vue"),
 		EditStation: () => import("@/components/modals/EditStation.vue"),

+ 1 - 0
frontend/src/store/modules/modalVisibility.js

@@ -11,6 +11,7 @@ const state = {
 		},
 		station: {
 			addSongToQueue: false,
+			requestSong: false,
 			editPlaylist: false,
 			createPlaylist: false,
 			editStation: false,