소스 검색

More work on Manage Station modal

Owen Diffey 4 년 전
부모
커밋
d7c7fe9458

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

@@ -0,0 +1,156 @@
+<template>
+	<div class="station-blacklist">
+		<p class="text-centered">
+			Blacklist a playlist to prevent all of its songs playing in this
+			station, or blacklist a song to prevent it playing.
+		</p>
+		<div class="tabs-container">
+			<div class="tab-selection">
+				<button
+					class="button is-default"
+					:class="{ selected: tab === 'playlists' }"
+					@click="showTab('playlists')"
+				>
+					Playlists
+				</button>
+				<button
+					class="button is-default"
+					:class="{ selected: tab === 'songs' }"
+					@click="showTab('songs')"
+				>
+					Songs
+				</button>
+			</div>
+			<div class="tab" v-show="tab === 'playlists'">
+				<div v-if="excludedPlaylists.length > 0">
+					<playlist-item
+						:playlist="playlist"
+						v-for="(playlist, index) in excludedPlaylists"
+						:key="'key-' + index"
+					>
+						<div class="icons-group" slot="actions">
+							<i
+								@click="deselectPlaylist(playlist._id)"
+								class="material-icons stop-icon"
+								content="Stop blacklisting songs from this playlist
+							"
+								v-tippy
+								>stop</i
+							>
+							<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
+							>
+						</div>
+					</playlist-item>
+				</div>
+				<p v-else class="nothing-here-text scrollable-list">
+					No playlists currently blacklisted.
+				</p>
+			</div>
+			<div class="tab" v-show="tab === 'songs'">
+				Blacklisting songs has yet to be added.
+			</div>
+		</div>
+	</div>
+</template>
+<script>
+import { mapActions, mapState, mapGetters } from "vuex";
+
+import Toast from "toasters";
+import PlaylistItem from "@/components/PlaylistItem.vue";
+
+import TabQueryHandler from "@/mixins/TabQueryHandler.vue";
+
+export default {
+	components: {
+		PlaylistItem
+	},
+	mixins: [TabQueryHandler],
+	data() {
+		return {
+			tab: "playlists"
+		};
+	},
+	computed: {
+		...mapState({
+			userId: state => state.user.auth.userId
+		}),
+		...mapState("modals/manageStation", {
+			station: state => state.station,
+			originalStation: state => state.originalStation,
+			excludedPlaylists: state => state.excludedPlaylists
+		}),
+		...mapGetters({
+			socket: "websockets/getSocket"
+		})
+	},
+	methods: {
+		showPlaylist(playlistId) {
+			this.editPlaylist(playlistId);
+			this.openModal({ sector: "station", modal: "editPlaylist" });
+		},
+		deselectPlaylist(id) {
+			this.socket.dispatch(
+				"stations.removeExcludedPlaylist",
+				this.station._id,
+				id,
+				res => {
+					new Toast(res.message);
+				}
+			);
+		},
+		...mapActions("modalVisibility", ["openModal"]),
+		...mapActions("user/playlists", ["editPlaylist"])
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+.station-blacklist {
+	.tabs-container {
+		margin-top: 10px;
+		.tab-selection {
+			display: flex;
+			.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(--dark-grey-3) !important;
+				color: var(--white) !important;
+			}
+		}
+		.tab {
+			padding: 15px 0;
+			border-radius: 0;
+			.playlist-item:not(:last-of-type) {
+				margin-bottom: 10px;
+			}
+		}
+	}
+}
+</style>

+ 37 - 1
frontend/src/components/modals/ManageStation/Tabs/Playlists.vue

@@ -34,6 +34,7 @@
 					>
 						<div class="icons-group" slot="actions">
 							<i
+								v-if="isOwnerOrAdmin()"
 								@click="deselectPlaylist(playlist._id)"
 								class="material-icons stop-icon"
 								content="Stop playing songs from this playlist
@@ -50,7 +51,11 @@
 								>edit</i
 							>
 							<i
-								v-else
+								v-if="
+									playlist.createdBy !== myUserId &&
+										(playlist.privacy === 'public' ||
+											isAdmin())
+								"
 								@click="showPlaylist(playlist._id)"
 								class="material-icons edit-icon"
 								content="View Playlist"
@@ -65,6 +70,23 @@
 				</p>
 			</div>
 			<div class="tab" v-show="tab === 'search'">
+				<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..."
+						/>
+					</p>
+					<p class="control">
+						<a class="button is-info" href="#"
+							><i class="material-icons icon-with-button"
+								>search</i
+							>Search</a
+						>
+					</p>
+				</div>
 				Searching genre and public user playlists has yet to be added.
 			</div>
 			<div
@@ -107,6 +129,8 @@
 								<i
 									v-if="
 										station.type === 'community' &&
+											(isOwnerOrAdmin() ||
+												station.partyMode) &&
 											!isSelected(playlist._id)
 									"
 									@click="selectPlaylist(playlist._id)"
@@ -122,6 +146,8 @@
 								<i
 									v-if="
 										station.type === 'community' &&
+											(isOwnerOrAdmin() ||
+												station.partyMode) &&
 											isSelected(playlist._id)
 									"
 									@click="deselectPlaylist(playlist._id)"
@@ -184,6 +210,7 @@ export default {
 			}
 		},
 		...mapState({
+			loggedIn: state => state.user.auth.loggedIn,
 			role: state => state.user.auth.role,
 			myUserId: state => state.user.auth.userId,
 			userId: state => state.user.auth.userId
@@ -291,6 +318,15 @@ export default {
 		);
 	},
 	methods: {
+		isOwner() {
+			return this.loggedIn && 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({ sector: "station", modal: "editPlaylist" });

+ 149 - 0
frontend/src/components/modals/ManageStation/Tabs/Search.vue

@@ -0,0 +1,149 @@
+<template>
+	<div class="search">
+		<div class="musare-search">
+			<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..."
+					/>
+				</p>
+				<p class="control">
+					<a class="button is-info" href="#"
+						><i class="material-icons icon-with-button">search</i
+						>Search</a
+					>
+				</p>
+			</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="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>
+
+			<div v-if="search.songs.results.length > 0" id="song-query-results">
+				<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-queue"
+							>
+								<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>
+		</div>
+	</div>
+</template>
+
+<script>
+import { mapState, mapGetters } from "vuex";
+
+import Toast from "toasters";
+import SearchYoutube from "@/mixins/SearchYoutube.vue";
+
+import SearchQueryItem from "../../../SearchQueryItem.vue";
+
+export default {
+	components: {
+		SearchQueryItem
+	},
+	mixins: [SearchYoutube],
+	computed: {
+		...mapState("modals/manageStation", {
+			station: state => state.station,
+			originalStation: state => state.originalStation
+		}),
+		...mapGetters({
+			socket: "websockets/getSocket"
+		})
+	},
+	methods: {
+		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 {
+							this.search.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.search.songs.results[index].isAddedToQueue = true;
+
+						new Toast(res.message);
+					}
+				});
+			}
+		}
+	}
+};
+</script>

+ 0 - 123
frontend/src/components/modals/ManageStation/Tabs/YoutubeSearch.vue

@@ -1,123 +0,0 @@
-<template>
-	<div class="youtube-search">
-		<label class="label"> Search for a song from 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="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>
-
-		<div v-if="search.songs.results.length > 0" id="song-query-results">
-			<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-queue"
-						>
-							<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>
-	</div>
-</template>
-
-<script>
-import { mapState, mapGetters } from "vuex";
-
-import Toast from "toasters";
-import SearchYoutube from "@/mixins/SearchYoutube.vue";
-
-import SearchQueryItem from "../../../SearchQueryItem.vue";
-
-export default {
-	components: {
-		SearchQueryItem
-	},
-	mixins: [SearchYoutube],
-	computed: {
-		...mapState("modals/manageStation", {
-			station: state => state.station,
-			originalStation: state => state.originalStation
-		}),
-		...mapGetters({
-			socket: "websockets/getSocket"
-		})
-	},
-	methods: {
-		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 {
-							this.search.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.search.songs.results[index].isAddedToQueue = true;
-
-						new Toast(res.message);
-					}
-				});
-			}
-		}
-	}
-};
-</script>

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

@@ -6,6 +6,7 @@
 					<div class="section tabs-container">
 						<div class="tab-selection">
 							<button
+								v-if="isOwnerOrAdmin()"
 								class="button is-default"
 								:class="{ selected: tab === 'settings' }"
 								@click="showTab('settings')"
@@ -13,6 +14,15 @@
 								Settings
 							</button>
 							<button
+								v-if="
+									isOwnerOrAdmin() ||
+										(loggedIn &&
+											station.type === 'community' &&
+											station.partyMode &&
+											((station.locked &&
+												isOwnerOrAdmin()) ||
+												!station.locked))
+								"
 								class="button is-default"
 								:class="{ selected: tab === 'playlists' }"
 								@click="showTab('playlists')"
@@ -21,26 +31,60 @@
 							</button>
 							<button
 								v-if="
-									station.type === 'community' &&
-										station.partyMode
+									loggedIn &&
+										station.type === 'community' &&
+										station.partyMode &&
+										((station.locked && isOwnerOrAdmin()) ||
+											!station.locked)
 								"
 								class="button is-default"
-								:class="{ selected: tab === 'youtube' }"
-								@click="showTab('youtube')"
+								:class="{ selected: tab === 'search' }"
+								@click="showTab('search')"
+							>
+								Search
+							</button>
+							<button
+								v-if="isOwnerOrAdmin()"
+								class="button is-default"
+								:class="{ selected: tab === 'blacklist' }"
+								@click="showTab('blacklist')"
 							>
-								YouTube
+								Blacklist
 							</button>
 						</div>
-						<settings class="tab" v-show="tab === 'settings'" />
-						<youtube-search
+						<settings
+							v-if="isOwnerOrAdmin()"
+							class="tab"
+							v-show="tab === 'settings'"
+						/>
+						<playlists
+							v-if="
+								isOwnerOrAdmin() ||
+									(loggedIn &&
+										station.type === 'community' &&
+										station.partyMode &&
+										((station.locked && isOwnerOrAdmin()) ||
+											!station.locked))
+							"
+							class="tab"
+							v-show="tab === 'playlists'"
+						/>
+						<search
 							v-if="
-								station.type === 'community' &&
-									station.partyMode
+								loggedIn &&
+									station.type === 'community' &&
+									station.partyMode &&
+									((station.locked && isOwnerOrAdmin()) ||
+										!station.locked)
 							"
 							class="tab"
-							v-show="tab === 'youtube'"
+							v-show="tab === 'search'"
+						/>
+						<blacklist
+							v-if="isOwnerOrAdmin()"
+							class="tab"
+							v-show="tab === 'blacklist'"
 						/>
-						<playlists class="tab" v-show="tab === 'playlists'" />
 					</div>
 				</div>
 				<div class="right-section">
@@ -66,7 +110,7 @@
 				<i class="material-icons icon-with-button">queue</i>
 				<span class="optional-desktop-only-text"> Request Song </span>
 			</button>
-			<div class="right">
+			<div v-if="isOwnerOrAdmin()" class="right">
 				<confirm @confirm="clearAndRefillStationQueue()">
 					<a class="button is-danger">
 						Clear and refill station queue
@@ -95,7 +139,8 @@ import Modal from "../../Modal.vue";
 import Queue from "../../../pages/Station/Sidebar/Queue.vue";
 import Settings from "./Tabs/Settings.vue";
 import Playlists from "./Tabs/Playlists.vue";
-import YoutubeSearch from "./Tabs/YoutubeSearch.vue";
+import Search from "./Tabs/Search.vue";
+import Blacklist from "./Tabs/Blacklist.vue";
 
 export default {
 	components: {
@@ -104,7 +149,8 @@ export default {
 		Queue,
 		Settings,
 		Playlists,
-		YoutubeSearch
+		Search,
+		Blacklist
 	},
 	mixins: [TabQueryHandler],
 	props: {
@@ -113,12 +159,15 @@ export default {
 	},
 	data() {
 		return {
-			tab: "settings"
+			tab: "playlists"
 		};
 	},
 	computed: {
 		...mapState({
-			loggedIn: state => state.user.auth.loggedIn
+			loggedIn: state => state.user.auth.loggedIn,
+			userId: state => state.user.auth.userId,
+			role: state => state.user.auth.role
+		}),
 		}),
 		...mapState("modals/manageStation", {
 			station: state => state.station,
@@ -166,6 +215,15 @@ export default {
 		this.clearStation();
 	},
 	methods: {
+		isOwner() {
+			return this.loggedIn && this.userId === this.station.owner;
+		},
+		isAdmin() {
+			return this.loggedIn && this.role === "admin";
+		},
+		isOwnerOrAdmin() {
+			return this.isOwner() || this.isAdmin();
+		},
 		clearAndRefillStationQueue() {
 			this.socket.dispatch(
 				"stations.clearAndRefillStationQueue",

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

@@ -23,6 +23,9 @@
 						<i
 							v-if="
 								station.type === 'community' &&
+									(userId === station.owner ||
+										role === 'admin' ||
+										station.partyMode) &&
 									!isSelected(playlist._id)
 							"
 							@click="selectPlaylist(playlist._id)"
@@ -38,6 +41,9 @@
 						<i
 							v-if="
 								station.type === 'community' &&
+									(userId === station.owner ||
+										role === 'admin' ||
+										station.partyMode) &&
 									isSelected(playlist._id)
 							"
 							@click="deselectPlaylist(playlist._id)"
@@ -92,6 +98,10 @@ export default {
 		};
 	},
 	computed: {
+		...mapState({
+			role: state => state.user.auth.role,
+			userId: state => state.user.auth.userId
+		}),
 		...mapState("station", {
 			station: state => state.station,
 			privatePlaylistQueueSelected: state =>

+ 1 - 1
frontend/src/pages/Station/Sidebar/Queue.vue

@@ -81,7 +81,7 @@
 			@click="
 				openModal({
 					sector: 'station',
-					modal: 'addSongToQueue'
+					modal: 'manageStation'
 				})
 			"
 		>

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

@@ -188,8 +188,7 @@ export default {
 			previousTeam: [
 				{
 					name: "Akira Laine",
-					bio:
-						"Co-Founder, Leader Developer, Designer and QA Tester.",
+					bio: "Co-Founder, Lead Developer, Designer and QA Tester.",
 					projects: ["MusareMeteor"],
 					active: "Sept 2015 - Feb 2016",
 					github: "darthmeme",