瀏覽代碼

feat(Station page): completely new design, still some improvements to be made for mobile

Signed-off-by: Jonathan <theflametrooper@gmail.com>
Jonathan 4 年之前
父節點
當前提交
8d837fe3ca

+ 0 - 247
frontend/src/pages/Station/SongsList.vue

@@ -1,247 +0,0 @@
-<template>
-	<sidebar :title="station.type === 'community' ? 'Queue' : 'Playlist'">
-		<template #content>
-			<article v-if="!noSong" class="media">
-				<figure v-if="currentSong.thumbnail" class="media-left">
-					<p class="image is-64x64">
-						<img
-							:src="currentSong.thumbnail"
-							onerror="this.src='/assets/notes-transparent.png'"
-						/>
-					</p>
-				</figure>
-				<div class="media-content">
-					<div class="content">
-						<p>
-							Current Song:
-							<strong>{{ currentSong.title }}</strong>
-							<br />
-							<small>{{ currentSong.artists }}</small>
-						</p>
-					</div>
-				</div>
-				<div class="media-right">
-					{{ utils.formatTime(currentSong.duration) }}
-				</div>
-			</article>
-			<p v-if="noSong" class="has-text-centered">
-				There is currently no song playing.
-			</p>
-			<hr v-if="noSong" />
-
-			<article
-				v-for="song in songsList"
-				:key="song.songId"
-				class="media"
-				:class="{ 'is-playing': currentSong.songId === song.songId }"
-			>
-				<div class="media-content">
-					<div
-						class="content"
-						style="display: block; padding-top: 10px"
-					>
-						<strong class="songTitle">{{ song.title }}</strong>
-						<small>{{ song.artists.join(", ") }}</small>
-						<div
-							v-if="
-								station.type === 'community' &&
-									station.partyMode === true
-							"
-						>
-							<small>
-								Requested by
-								<b>
-									<user-id-to-username
-										:user-id="song.requestedBy"
-										:link="true"
-									/>
-								</b>
-							</small>
-							<i
-								v-if="isOwnerOnly() || isAdminOnly()"
-								class="material-icons"
-								style="vertical-align: middle"
-								@click="removeFromQueue(song.songId)"
-								>delete_forever</i
-							>
-						</div>
-					</div>
-				</div>
-				<div class="media-right">
-					{{ utils.formatTime(song.duration) }}
-				</div>
-			</article>
-			<div
-				v-if="
-					station.type === 'community' &&
-						loggedIn &&
-						station.partyMode === true
-				"
-			>
-				<button
-					v-if="
-						(station.locked && isOwnerOnly()) ||
-							!station.locked ||
-							(station.locked &&
-								isAdminOnly() &&
-								dismissedWarning)
-					"
-					class="button add-to-queue"
-					@click="
-						openModal({
-							sector: 'station',
-							modal: 'addSongToQueue'
-						})
-					"
-				>
-					Add Song to Queue
-				</button>
-				<button
-					v-if="
-						station.locked &&
-							isAdminOnly() &&
-							!isOwnerOnly() &&
-							!dismissedWarning
-					"
-					class="button add-to-queue add-to-queue-warning"
-					@click="dismissedWarning = true"
-				>
-					THIS STATION'S QUEUE IS LOCKED.
-				</button>
-				<button
-					v-if="station.locked && !isAdminOnly() && !isOwnerOnly()"
-					class="button add-to-queue add-to-queue-disabled"
-				>
-					THIS STATION'S QUEUE IS LOCKED.
-				</button>
-			</div>
-		</template>
-	</sidebar>
-</template>
-
-<script>
-import { mapState, mapActions } from "vuex";
-import Toast from "toasters";
-
-import utils from "../../../js/utils";
-
-import Sidebar from "../../components/Sidebar.vue";
-
-import UserIdToUsername from "../../components/common/UserIdToUsername.vue";
-
-export default {
-	components: { UserIdToUsername, Sidebar },
-	data() {
-		return {
-			utils,
-			dismissedWarning: false
-		};
-	},
-	computed: mapState({
-		loggedIn: state => state.user.auth.loggedIn,
-		userId: state => state.user.auth.userId,
-		role: state => state.user.auth.role,
-		station: state => state.station.station,
-		currentSong: state => state.station.currentSong,
-		songsList: state => state.station.songsList,
-		noSong: state => state.station.noSong
-	}),
-	methods: {
-		isOwnerOnly() {
-			return this.loggedIn && this.userId === this.station.owner;
-		},
-		isAdminOnly() {
-			return this.loggedIn && this.role === "admin";
-		},
-		removeFromQueue(songId) {
-			window.socket.emit(
-				"stations.removeFromQueue",
-				this.station._id,
-				songId,
-				res => {
-					if (res.status === "success") {
-						new Toast({
-							content:
-								"Successfully removed song from the queue.",
-							timeout: 4000
-						});
-					} else new Toast({ content: res.message, timeout: 8000 });
-				}
-			);
-		},
-		...mapActions("modals", ["openModal"])
-	}
-};
-</script>
-
-<style lang="scss" scoped>
-@import "../../styles/global.scss";
-
-.media {
-	padding: 0 25px;
-}
-
-.media.is-playing {
-	background-color: $musare-blue;
-	color: white;
-}
-
-.media-content .content {
-	min-height: 64px;
-	display: flex;
-	align-items: center;
-	color: inherit;
-}
-
-.content p strong {
-	word-break: break-word;
-}
-
-.content p small {
-	word-break: break-word;
-}
-
-.add-to-queue {
-	width: 100%;
-	margin-top: 25px;
-	height: 40px;
-	border-radius: 0;
-	background: rgb(3, 169, 244);
-	color: $white !important;
-	border: 0;
-	&:active,
-	&:focus {
-		border: 0;
-	}
-}
-
-.add-to-queue.add-to-queue-warning {
-	background-color: red;
-}
-
-.add-to-queue.add-to-queue-disabled {
-	background-color: gray;
-}
-.add-to-queue.add-to-queue-disabled:focus {
-	background-color: gray;
-}
-
-.add-to-queue:focus {
-	background: $primary-color;
-}
-
-.media-right {
-	line-height: 64px;
-}
-
-.songTitle {
-	word-wrap: break-word;
-	overflow: hidden;
-	text-overflow: ellipsis;
-	display: -webkit-box;
-	-webkit-box-orient: vertical;
-	-webkit-line-clamp: 2;
-	line-height: 20px;
-	max-height: 40px;
-}
-</style>

+ 0 - 545
frontend/src/pages/Station/StationHeader.vue

@@ -1,545 +0,0 @@
-<template>
-	<div>
-		<nav class="nav">
-			<div class="nav-left">
-				<router-link class="nav-item is-brand" :to="{ path: '/' }">
-					<img
-						:src="`${this.siteSettings.logo_white}`"
-						:alt="`${this.siteSettings.siteName}` || `Musare`"
-					/>
-				</router-link>
-			</div>
-
-			<div class="nav-center stationDisplayName">
-				<h4>{{ station.displayName }}</h4>
-			</div>
-
-			<span
-				class="nav-toggle"
-				tab-index="0"
-				@click="controlBar = !controlBar"
-				@keyup.enter="isMobile = !isMobile"
-			>
-				<span />
-				<span />
-				<span />
-			</span>
-
-			<div class="nav-right nav-menu" :class="{ 'is-active': isMobile }">
-				<router-link
-					v-if="role === 'admin'"
-					class="nav-item is-tab admin"
-					:to="{ path: '/admin' }"
-				>
-					<strong>Admin</strong>
-				</router-link>
-				<span v-if="loggedIn" class="grouped">
-					<router-link
-						class="nav-item is-tab"
-						:to="{ path: '/u/' + username }"
-						>Profile</router-link
-					>
-					<router-link class="nav-item is-tab" to="/settings"
-						>Settings</router-link
-					>
-					<a class="nav-item is-tab" @click="logout()">Logout</a>
-				</span>
-				<span v-else class="grouped">
-					<a
-						class="nav-item"
-						href="#"
-						@click="openModal({ sector: 'header', modal: 'login' })"
-						>Login</a
-					>
-					<a
-						class="nav-item"
-						href="#"
-						@click="
-							openModal({ sector: 'header', modal: 'register' })
-						"
-						>Register</a
-					>
-				</span>
-			</div>
-		</nav>
-		<div class="control-sidebar" :class="{ 'show-controlBar': controlBar }">
-			<div class="inner-wrapper">
-				<div v-if="isOwner()">
-					<a class="sidebar-item" href="#" @click="settings()">
-						<span class="icon">
-							<i class="material-icons">settings</i>
-						</span>
-						<span class="icon-purpose">Station settings</span>
-					</a>
-					<a
-						class="sidebar-item"
-						href="#"
-						@click="$parent.skipStation()"
-					>
-						<span class="icon">
-							<i class="material-icons">skip_next</i>
-						</span>
-						<span class="icon-purpose">Skip current song</span>
-					</a>
-					<a
-						v-if="stationPaused"
-						class="sidebar-item"
-						href="#"
-						@click="$parent.resumeStation()"
-					>
-						<span class="icon">
-							<i class="material-icons">play_arrow</i>
-						</span>
-						<span class="icon-purpose">Resume station</span>
-					</a>
-					<a
-						v-if="!stationPaused"
-						class="sidebar-item"
-						href="#"
-						@click="$parent.pauseStation()"
-					>
-						<span class="icon">
-							<i class="material-icons">pause</i>
-						</span>
-						<span class="icon-purpose">Pause station</span>
-					</a>
-					<hr />
-				</div>
-				<a
-					v-if="localPaused"
-					class="sidebar-item"
-					href="#"
-					@click="$parent.resumeLocalStation()"
-				>
-					<span class="icon">
-						<i class="material-icons">play_arrow</i>
-					</span>
-					<span class="icon-purpose">Resume station ;pca;</span>
-				</a>
-				<a
-					v-if="!localPaused"
-					class="sidebar-item"
-					href="#"
-					@click="$parent.pauseLocalStation()"
-				>
-					<span class="icon">
-						<i class="material-icons">pause</i>
-					</span>
-					<span class="icon-purpose">Pause station local</span>
-				</a>
-				<div v-if="loggedIn">
-					<a
-						v-if="station.type === 'official'"
-						class="sidebar-item"
-						href="#"
-						@click="
-							openModal({
-								sector: 'station',
-								modal: 'addSongToQueue'
-							})
-						"
-					>
-						<span class="icon">
-							<i class="material-icons">queue</i>
-						</span>
-						<span class="icon-purpose">Add song to queue</span>
-					</a>
-					<a
-						v-if="!noSong"
-						class="sidebar-item skip-votes"
-						href="#"
-						@click="$parent.voteSkipStation()"
-					>
-						<span class="icon">
-							<i class="material-icons">skip_next</i>
-						</span>
-						<span class="count">{{ currentSong.skipVotes }}</span>
-						<span class="icon-purpose">Skip current song</span>
-					</a>
-					<a
-						v-if="!noSong && !currentSong.simpleSong"
-						class="sidebar-item"
-						href="#"
-						@click="
-							openModal({
-								sector: 'station',
-								modal: 'report'
-							})
-						"
-					>
-						<span class="icon">
-							<i class="material-icons">report</i>
-						</span>
-						<span class="icon-purpose">Report a song</span>
-					</a>
-					<a
-						v-if="!noSong"
-						class="sidebar-item"
-						href="#"
-						@click="
-							openModal({
-								sector: 'station',
-								modal: 'addSongToPlaylist'
-							})
-						"
-					>
-						<span class="icon">
-							<i class="material-icons">playlist_add</i>
-						</span>
-						<span class="icon-purpose"
-							>Add current song to playlist</span
-						>
-					</a>
-					<hr v-if="!noSong" />
-				</div>
-				<a
-					v-if="
-						station.partyMode === true ||
-							station.type === 'official'
-					"
-					class="sidebar-item"
-					href="#"
-					@click="
-						toggleSidebar({
-							sector: 'station',
-							sidebar: 'songslist'
-						})
-					"
-				>
-					<span class="icon">
-						<i class="material-icons">queue_music</i>
-					</span>
-					<span class="icon-purpose">Show the station queue</span>
-				</a>
-				<a
-					class="sidebar-item"
-					href="#"
-					@click="
-						toggleSidebar({ sector: 'station', sidebar: 'users' })
-					"
-				>
-					<span class="icon">
-						<i class="material-icons">people</i>
-					</span>
-					<span class="icon-purpose"
-						>Display users in the station</span
-					>
-				</a>
-				<a
-					class="sidebar-item"
-					href="#"
-					@click="$parent.togglePlayerDebugBox()"
-					@dblclick="$parent.resetPlayerDebugBox()"
-				>
-					<span class="icon">
-						<i class="material-icons">bug_report</i>
-					</span>
-					<span class="icon-purpose">Toggle debug player box</span>
-				</a>
-			</div>
-		</div>
-	</div>
-</template>
-
-<script>
-import { mapState, mapActions } from "vuex";
-
-export default {
-	data() {
-		return {
-			title: this.$route.params.id,
-			isMobile: false,
-			controlBar: false,
-			frontendDomain: "",
-			siteSettings: {
-				logo: "",
-				siteName: ""
-			}
-		};
-	},
-	computed: mapState({
-		loggedIn: state => state.user.auth.loggedIn,
-		userId: state => state.user.auth.userId,
-		username: state => state.user.auth.username,
-		role: state => state.user.auth.role,
-		station: state => state.station.station,
-		stationPaused: state => state.station.stationPaused,
-		localPaused: state => state.station.localPaused,
-		noSong: state => state.station.noSong,
-		currentSong: state => state.station.currentSong
-	}),
-	mounted() {
-		lofig.get("frontendDomain").then(frontendDomain => {
-			this.frontendDomain = frontendDomain;
-		});
-
-		lofig.get("siteSettings").then(siteSettings => {
-			this.siteSettings = siteSettings;
-		});
-	},
-	methods: {
-		isOwner() {
-			return (
-				this.loggedIn &&
-				(this.role === "admin" || this.userId === this.station.owner)
-			);
-		},
-		settings() {
-			this.editStation({
-				_id: this.station._id,
-				name: this.station.name,
-				type: this.station.type,
-				partyMode: this.station.partyMode,
-				description: this.station.description,
-				privacy: this.station.privacy,
-				displayName: this.station.displayName,
-				locked: this.station.locked
-			});
-			this.openModal({
-				sector: "station",
-				modal: "editStation"
-			});
-		},
-		...mapActions("modals", ["openModal"]),
-		...mapActions("sidebars", ["toggleSidebar"]),
-		...mapActions("station", ["editStation"]),
-		...mapActions("user/auth", ["logout"])
-	}
-};
-</script>
-
-<style lang="scss" scoped>
-@import "../../styles/global.scss";
-
-.nav {
-	background-color: $primary-color;
-	line-height: 64px;
-	overflow: hidden;
-	border-radius: 0% 0% 33% 33% / 0% 0% 7% 7%;
-	transition: border-radius 0.1s 0s linear;
-
-	.is-brand {
-		font-size: 2.1rem !important;
-		line-height: 38px !important;
-		padding: 0 20px;
-		font-family: Pacifico, cursive;
-
-		img {
-			max-height: 38px;
-			color: $musare-blue;
-		}
-	}
-}
-
-.header-sidebar-active .nav {
-	border-radius: 0% 0% 0% 33% / 0% 0% 0% 7%;
-}
-
-a.nav-item {
-	color: $white;
-	font-size: 17px;
-
-	&:hover {
-		color: $white;
-	}
-
-	padding: 0 12px;
-	.icon {
-		height: 64px;
-		i {
-			font-size: 2rem;
-			line-height: 64px;
-			height: 64px;
-			width: 34px;
-		}
-	}
-}
-
-a.nav-item.is-tab:hover {
-	border-bottom: 1px solid transparent;
-	border-top: 1px solid $white;
-}
-
-.admin strong {
-	color: $purple;
-}
-
-.grouped {
-	margin: 0;
-	display: flex;
-	text-decoration: none;
-}
-
-.skip-votes {
-	flex-direction: column;
-	.count {
-		font-size: 18px;
-	}
-}
-
-.nav {
-}
-
-.nav-toggle {
-	height: 64px;
-
-	span {
-		background-color: $white;
-	}
-
-	&:hover,
-	&:active {
-		background-color: darken($musare-blue, 10%);
-	}
-}
-
-@media screen and (max-width: 998px) {
-	.nav-menu {
-		background-color: $white;
-		box-shadow: 0 4px 7px rgba(10, 10, 10, 0.1);
-		left: 0;
-		display: none;
-		right: 0;
-		top: 100%;
-		position: absolute;
-	}
-	.nav-toggle {
-		display: block;
-	}
-}
-
-.logo {
-	font-size: 2.1rem;
-	line-height: 64px;
-	padding-left: 20px !important;
-	padding-right: 20px !important;
-}
-
-.nav-center {
-	display: flex;
-	align-items: center;
-	color: $primary-color;
-	font-size: 22px;
-	position: absolute;
-	margin: auto;
-	top: 50%;
-	left: 50%;
-	transform: translate(-50%, -50%);
-}
-
-.nav-right.is-active .nav-item {
-	background: $primary-color;
-	border: 0;
-}
-
-.hidden {
-	display: none;
-}
-
-.control-sidebar {
-	position: fixed;
-	z-index: 1;
-	top: 0;
-	left: 0;
-	width: 64px;
-	height: 100vh;
-	background-color: $primary-color;
-	box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16),
-		0 2px 10px 0 rgba(0, 0, 0, 0.12);
-
-	@media (max-width: 998px) {
-		display: none;
-	}
-	.inner-wrapper {
-		@media (min-width: 999px) {
-			.mobile-only {
-				display: none;
-			}
-			.desktop-only {
-				display: flex;
-			}
-		}
-		@media (max-width: 998px) {
-			.mobile-only {
-				display: flex;
-			}
-			.desktop-only {
-				display: none;
-				visibility: hidden;
-			}
-		}
-	}
-}
-
-.show-controlBar {
-	display: block;
-}
-
-.inner-wrapper {
-	top: 64px;
-	position: relative;
-}
-
-.control-sidebar .material-icons {
-	width: 100%;
-	font-size: 2rem;
-}
-.control-sidebar .sidebar-item {
-	font-size: 2rem;
-	height: 50px;
-	color: $white;
-	-webkit-box-align: center;
-	-ms-flex-align: center;
-	align-items: center;
-	display: -webkit-box;
-	display: -ms-flexbox;
-	display: flex;
-	-webkit-box-flex: 0;
-	-ms-flex-positive: 0;
-	flex-grow: 0;
-	-ms-flex-negative: 0;
-	flex-shrink: 0;
-	-webkit-box-pack: center;
-	-ms-flex-pack: center;
-	justify-content: center;
-	width: 100%;
-	position: relative;
-}
-.control-sidebar .sidebar-top-hr {
-	margin: 0 0 20px 0;
-}
-
-.sidebar-item .icon-purpose {
-	visibility: hidden;
-	width: 160px;
-	font-size: 12px;
-	background-color: rgba(3, 169, 244, 0.8);
-	color: $white;
-	text-align: center;
-	border-radius: 6px;
-	padding: 5px;
-	position: absolute;
-	z-index: 1;
-	left: 115%;
-	opacity: 0;
-	transition: opacity 0.5s;
-	display: none;
-}
-
-.sidebar-item .icon-purpose::after {
-	content: "";
-	position: absolute;
-	top: 50%;
-	right: 100%;
-	margin-top: -5px;
-	border-width: 5px;
-	border-style: solid;
-	border-color: transparent rgba(3, 169, 244, 0.8) transparent transparent;
-}
-
-.sidebar-item:hover .icon-purpose {
-	visibility: visible;
-	opacity: 1;
-	display: block;
-}
-</style>

+ 0 - 43
frontend/src/pages/Station/UsersList.vue

@@ -1,43 +0,0 @@
-<template>
-	<sidebar title="Users">
-		<template #content>
-			<h5 class="has-text-centered">Total users: {{ userCount }}</h5>
-			<aside class="menu">
-				<ul class="menu-list">
-					<li v-for="(username, index) in users" :key="index">
-						<router-link
-							:to="{ name: 'profile', params: { username } }"
-							target="_blank"
-						>
-							{{ username }}
-						</router-link>
-					</li>
-				</ul>
-			</aside>
-		</template>
-	</sidebar>
-</template>
-
-<script>
-import { mapState } from "vuex";
-
-import Sidebar from "../../components/Sidebar.vue";
-
-export default {
-	components: { Sidebar },
-	computed: mapState({
-		users: state => state.station.users,
-		userCount: state => state.station.userCount
-	})
-};
-</script>
-
-<style lang="scss" scoped>
-.menu {
-	padding: 0 20px;
-}
-
-.menu-list li a:hover {
-	color: #000 !important;
-}
-</style>

+ 177 - 0
frontend/src/pages/Station/components/CurrentlyPlaying.vue

@@ -0,0 +1,177 @@
+<template>
+	<div id="currently-playing">
+		<div
+			v-if="currentSong.ytThumbnail"
+			class="thumbnail"
+			id="yt-thumbnail"
+			:style="{
+				'background-image': 'url(' + currentSong.ytThumbnail + ')'
+			}"
+		/>
+		<img
+			v-if="currentSong.thumbnail"
+			class="thumbnail"
+			:src="currentSong.thumbnail"
+			onerror="this.src='/assets/notes-transparent.png'"
+		/>
+		<div id="song-info">
+			<h6>Currently playing...</h6>
+			<h4
+				id="song-title"
+				:style="!currentSong.artists ? { fontSize: '17px' } : null"
+			>
+				{{ currentSong.title }}
+			</h4>
+			<h5 id="song-artists" v-if="currentSong.artists">
+				{{ currentSong.artists.join(", ") }}
+			</h5>
+			<p id="song-request-time">
+				Requested <strong>15 minutes ago</strong>
+			</p>
+			<div id="song-actions">
+				<button
+					class="button"
+					id="report-icon"
+					v-if="!currentSong.simpleSong"
+					@click="
+						openModal({
+							sector: 'station',
+							modal: 'report'
+						})
+					"
+				>
+					<i class="material-icons icon-with-button">flag</i>Report
+				</button>
+				<a
+					class="button"
+					id="youtube-icon"
+					target="_blank"
+					:href="
+						`https://www.youtube.com/watch?v=${currentSong.songId}`
+					"
+				>
+					<div class="icon"></div>
+				</a>
+			</div>
+		</div>
+	</div>
+</template>
+
+<script>
+import { mapState, mapActions } from "vuex";
+
+export default {
+	computed: {
+		...mapState("station", {
+			currentSong: state => state.currentSong
+		})
+	},
+	methods: {
+		...mapActions("modals", ["openModal"])
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+@import "../../../styles/global.scss";
+
+#currently-playing {
+	display: flex;
+	flex-direction: row;
+	align-items: center;
+	width: 100%;
+	height: 100%;
+	padding: 10px 20px;
+
+	.thumbnail {
+		min-width: 140px;
+		min-height: 140px;
+	}
+
+	#yt-thumbnail {
+		background: url("/assets/notes-transparent.png") no-repeat center center;
+		background-size: cover;
+	}
+
+	@media (max-width: 2200px) {
+		#song-actions {
+			.button {
+				padding: 0 10px !important;
+			}
+		}
+
+		#song-info {
+			margin-left: 0;
+		}
+	}
+
+	#song-info {
+		display: flex;
+		flex-direction: column;
+		justify-content: center;
+		margin-left: 20px;
+
+		*:not(i) {
+			margin: 0;
+			font-family: Karla, Arial, sans-serif;
+		}
+
+		h6 {
+			color: $musare-blue !important;
+			font-weight: bold;
+			font-size: 17px;
+		}
+
+		#song-title {
+			margin-top: 7px;
+			font-size: 22px;
+		}
+
+		#song-artists {
+			font-size: 16px;
+		}
+
+		#song-request-time {
+			font-size: 12px;
+			margin-top: 7px;
+			color: $dark-grey;
+		}
+
+		#song-actions {
+			margin-top: 10px;
+
+			.button {
+				color: #fff;
+				padding: 0 15px;
+				border: 0;
+			}
+
+			#report-icon {
+				background-color: #6b6a6a;
+			}
+
+			#youtube-icon {
+				background-color: #bd2e2e;
+
+				&:after {
+					content: "View on YouTube";
+
+					@media (max-width: 1800px) {
+						content: "Open";
+					}
+				}
+
+				.icon {
+					margin-right: 3px;
+					height: 20px;
+					width: 20px;
+					-webkit-mask: url("/assets/social/youtube.svg") no-repeat
+						center;
+					mask: url("/assets/social/youtube.svg") no-repeat center;
+					background-color: #fff;
+				}
+			}
+		}
+	}
+}
+</style>

+ 136 - 0
frontend/src/pages/Station/components/Sidebar/Queue/QueueItem.vue

@@ -0,0 +1,136 @@
+<template>
+	<div class="queue-item">
+		<div id="thumbnail-and-info">
+			<img
+				id="thumbnail"
+				:src="song.ytThumbnail ? song.ytThumbnail : song.thumbnail"
+				onerror="this.src='/assets/notes-transparent.png'"
+			/>
+			<div id="song-info">
+				<h4
+					id="song-title"
+					:style="
+						song.artists.length < 1 ? { fontSize: '16px' } : null
+					"
+				>
+					{{ song.title }}
+				</h4>
+				<h5 id="song-artists" v-if="song.artists">
+					{{ song.artists.join(", ") }}
+				</h5>
+				<p
+					id="song-request-time"
+					v-if="
+						station.type === 'community' &&
+							station.partyMode === true
+					"
+				>
+					Requested by
+					<strong>
+						<user-id-to-username
+							:user-id="song.requestedBy"
+							:link="true"
+						/>
+					</strong>
+				</p>
+			</div>
+		</div>
+		<div id="duration-and-actions">
+			<p id="song-duration">
+				{{ utils.formatTime(song.duration) }}
+			</p>
+			<i
+				v-if="$parent.isOwnerOnly() || $parent.isAdminOnly()"
+				class="material-icons"
+				id="remove-queue-item"
+				@click="$parent.removeFromQueue(song.songId)"
+				>delete_forever</i
+			>
+		</div>
+	</div>
+</template>
+
+<script>
+import UserIdToUsername from "../../../../../components/common/UserIdToUsername.vue";
+import utils from "../../../../../../js/utils";
+
+export default {
+	components: { UserIdToUsername },
+	props: {
+		song: {
+			type: Object,
+			default: () => {}
+		},
+		station: {
+			type: Object,
+			default: () => {
+				return { type: "community", partyMode: false };
+			}
+		}
+	},
+	data() {
+		return {
+			utils
+		};
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+@import "../../../../../styles/global.scss";
+
+.queue-item {
+	display: flex;
+	flex-direction: row;
+	align-items: center;
+	justify-content: space-between;
+	padding: 5px 10px;
+	border: 1px solid $light-grey-2;
+	border-radius: 3px;
+
+	#thumbnail-and-info,
+	#duration-and-actions {
+		display: flex;
+		align-items: center;
+	}
+
+	#thumbnail {
+		width: 80px;
+		height: 80px;
+	}
+
+	#song-info {
+		display: flex;
+		flex-direction: column;
+		justify-content: center;
+		margin-left: 25px;
+
+		*:not(i) {
+			margin: 0;
+			font-family: Karla, Arial, sans-serif;
+		}
+
+		#song-title {
+			font-size: 22px;
+		}
+
+		#song-artists {
+			font-size: 16px;
+		}
+
+		#song-request-time {
+			font-size: 12px;
+			margin-top: 7px;
+		}
+	}
+
+	#song-duration {
+		font-size: 22px;
+	}
+
+	#remove-queue-item {
+		cursor: pointer;
+		margin-left: 10px;
+	}
+}
+</style>

+ 164 - 0
frontend/src/pages/Station/components/Sidebar/Queue/index.vue

@@ -0,0 +1,164 @@
+<template>
+	<div id="queue">
+		<div id="queue-items">
+			<queue-item
+				v-for="song in songsList"
+				:key="song.songId"
+				:song="song"
+				:station="{ type: station.type, partyMode: station.partyMode }"
+			/>
+			<p class="nothing-here" v-if="songsList.length < 1">
+				There are no songs currently queued
+			</p>
+		</div>
+		<div
+			id="queue-buttons"
+			v-if="loggedIn && station.type === 'community' && station.partyMode"
+		>
+			<button
+				id="add-song-to-queue"
+				class="button is-primary"
+				v-if="
+					(station.locked && isOwnerOnly()) ||
+						!station.locked ||
+						(station.locked && isAdminOnly() && dismissedWarning)
+				"
+				@click="
+					openModal({
+						sector: 'station',
+						modal: 'addSongToQueue'
+					})
+				"
+			>
+				<i class="material-icons icon-with-button">queue</i>
+				<span class="optional-desktop-only-text">
+					Add Song To Queue
+				</span>
+			</button>
+			<button
+				v-if="
+					station.locked &&
+						isAdminOnly() &&
+						!isOwnerOnly() &&
+						!dismissedWarning
+				"
+				class="button"
+				@click="dismissedWarning = true"
+			>
+				THIS STATION'S QUEUE IS LOCKED.
+			</button>
+			<button
+				v-if="station.locked && !isAdminOnly() && !isOwnerOnly()"
+				class="button"
+			>
+				THIS STATION'S QUEUE IS LOCKED.
+			</button>
+		</div>
+	</div>
+</template>
+
+<script>
+import { mapActions, mapState } from "vuex";
+import Toast from "toasters";
+
+import QueueItem from "./QueueItem.vue";
+
+export default {
+	components: { QueueItem },
+	data() {
+		return {
+			dismissedWarning: false
+		};
+	},
+	computed: mapState({
+		loggedIn: state => state.user.auth.loggedIn,
+		userId: state => state.user.auth.userId,
+		userRole: state => state.user.auth.role,
+		station: state => state.station.station,
+		songsList: state => state.station.songsList,
+		noSong: state => state.station.noSong
+	}),
+	methods: {
+		isOwnerOnly() {
+			return this.loggedIn && this.userId === this.station.owner;
+		},
+		isAdminOnly() {
+			return this.loggedIn && this.userRole === "admin";
+		},
+		removeFromQueue(songId) {
+			window.socket.emit(
+				"stations.removeFromQueue",
+				this.station._id,
+				songId,
+				res => {
+					if (res.status === "success") {
+						new Toast({
+							content:
+								"Successfully removed song from the queue.",
+							timeout: 4000
+						});
+					} else new Toast({ content: res.message, timeout: 8000 });
+				}
+			);
+		},
+		...mapActions("modals", ["openModal"])
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+@import "../../../../../styles/global.scss";
+
+.night-mode {
+	#queue-items {
+		background-color: #222 !important;
+		border: 0 !important;
+	}
+}
+
+#queue {
+	::-webkit-scrollbar {
+		width: 10px;
+	}
+
+	::-webkit-scrollbar-track {
+		background-color: #fff;
+		border: 1px solid $light-grey-2;
+	}
+
+	::-webkit-scrollbar-thumb {
+		background-color: $dark-grey;
+
+		&:hover {
+			background-color: darken($dark-grey, 10%);
+		}
+	}
+
+	#queue-items {
+		background-color: #fff;
+		border: 1px solid $light-grey-2;
+		border-radius: 0 0 5px 5px;
+		width: 100%;
+		overflow: auto;
+		height: inherit;
+		padding: 10px;
+
+		@media (min-width: 1040px) {
+			margin-bottom: 20px;
+		}
+
+		.queue-item:not(:last-of-type) {
+			margin-bottom: 10px;
+		}
+	}
+
+	#add-song-to-queue {
+		width: 100%;
+		height: 45px;
+
+		@media (min-width: 1040px) {
+			border-radius: 5px;
+		}
+	}
+}
+</style>

+ 54 - 0
frontend/src/pages/Station/components/Sidebar/Users/index.vue

@@ -0,0 +1,54 @@
+<template>
+	<div id="users">
+		<h5 class="has-text-centered">Total users: {{ userCount }}</h5>
+		<aside class="menu">
+			<ul class="menu-list">
+				<li v-for="(username, index) in users" :key="index">
+					<router-link
+						:to="{ name: 'profile', params: { username } }"
+						target="_blank"
+					>
+						{{ username }}
+					</router-link>
+				</li>
+			</ul>
+		</aside>
+	</div>
+</template>
+
+<script>
+import { mapState } from "vuex";
+
+export default {
+	computed: mapState({
+		users: state => state.station.users,
+		userCount: state => state.station.userCount
+	})
+};
+</script>
+
+<style lang="scss" scoped>
+@import "../../../../../styles/global.scss";
+
+.night-mode {
+	#users {
+		background-color: #222 !important;
+		border: 0 !important;
+	}
+
+	a {
+		color: #ddd;
+	}
+}
+
+#users {
+	background-color: #fff;
+	border: 1px solid $light-grey-2;
+	margin-bottom: 20px;
+	padding: 10px;
+
+	.menu-list li a:hover {
+		color: #000 !important;
+	}
+}
+</style>

+ 86 - 0
frontend/src/pages/Station/components/Sidebar/index.vue

@@ -0,0 +1,86 @@
+<template>
+	<div id="tabs-container">
+		<div id="tab-selection">
+			<button
+				class="button is-default"
+				:class="{ selected: tab === 'queue' }"
+				@click="tab = 'queue'"
+			>
+				Queue
+			</button>
+			<button
+				class="button is-default"
+				:class="{ selected: tab === 'users' }"
+				@click="tab = 'users'"
+			>
+				Users
+			</button>
+		</div>
+		<queue class="tab" v-if="tab === 'queue'" />
+		<users class="tab" v-if="tab === 'users'" />
+	</div>
+</template>
+
+<script>
+import { mapActions, mapState } from "vuex";
+
+import Queue from "./Queue/index.vue";
+import Users from "./Users/index.vue";
+
+export default {
+	components: { Queue, Users },
+	data() {
+		return {
+			tab: "queue"
+		};
+	},
+	computed: mapState({
+		users: state => state.station.users,
+		userCount: state => state.station.userCount
+	}),
+	methods: {
+		...mapActions("modals", ["openModal"])
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+@import "../../../../styles/global.scss";
+
+#tabs-container {
+	width: 100%;
+	top: 0;
+	position: absolute;
+}
+
+#tab-selection {
+	display: flex;
+
+	.button {
+		border-radius: 0;
+		border: 0;
+		text-transform: uppercase;
+		font-size: 17px;
+		width: calc(50% - 2.5px);
+		color: #222;
+		background-color: #ddd;
+
+		&:not(:first-of-type) {
+			margin-left: 5px;
+		}
+	}
+
+	.selected {
+		background-color: #222;
+		color: #fff;
+	}
+}
+
+.tab {
+	height: 670px;
+
+	@media (max-width: 1040px) {
+		height: fit-content;
+	}
+}
+</style>

+ 670 - 917
frontend/src/pages/Station/index.vue

@@ -3,78 +3,118 @@
 		<metadata v-if="exists && !loading" :title="`${station.displayName}`" />
 		<metadata v-else-if="!exists && !loading" :title="`Not found`" />
 
-		<station-header
-			v-if="exists"
-			:class="{ 'header-sidebar-active': sidebarActive }"
-		/>
+		<main-header v-if="exists" />
 
-		<div class="station-parent">
+		<div id="station-outer-container">
 			<div v-show="loading" class="progress" />
-			<div v-show="!loading && exists" class="station">
-				<div v-show="noSong" class="no-song">
-					<h1>No song is currently playing</h1>
-					<h4
-						v-if="
-							station.type === 'community' &&
-								station.partyMode &&
-								this.loggedIn &&
-								(!station.locked ||
-									(station.locked &&
-										this.userId === station.owner))
-						"
-					>
-						<a
-							href="#"
-							class="no-song"
-							@click="
-								openModal({
-									sector: 'station',
-									modal: 'addSongToQueue'
-								})
-							"
-							>Add a song to the queue</a
-						>
-					</h4>
-					<h4
-						v-if="
-							station.type === 'community' &&
-								!station.partyMode &&
-								this.userId === station.owner &&
-								!station.privatePlaylist
-						"
-					>
-						<a
-							href="#"
-							class="no-song"
-							@click="
-								openModal({
-									sector: 'station',
-									modal: 'editStation'
-								})
-							"
-							>Play a private playlist</a
+			<div v-show="!loading && exists" id="station-inner-container">
+				<div id="upper-row" class="row">
+					<div id="about-station-container" class="quadrant">
+						<div class="row" id="station-name">
+							<h1>
+								<!-- {{ station.displayName }} -->Owen's Station
+							</h1>
+							<a href="#">
+								<!-- TODO: Add favourite functionality -->
+								<i class="material-icons">star</i>
+							</a>
+						</div>
+
+						<p>
+							<!-- {{ station.description }} -->Welcome to Owen's
+							channel! The description will go here and we will
+							explain what this station is for and what genres of
+							songs it has. Enjoy!!!
+						</p>
+
+						<div id="admin-buttons" v-if="isOwnerOrAdmin()">
+							<!-- (Admin) Station Settings Button -->
+							<button
+								class="button is-primary"
+								@click="openSettings()"
+							>
+								<i class="material-icons icon-with-button"
+									>settings</i
+								>
+								<span class="optional-desktop-only-text">
+									Station settings
+								</span>
+							</button>
+
+							<!-- Debug Box -->
+							<button
+								class="button is-primary"
+								@click="togglePlayerDebugBox()"
+								@dblclick="resetPlayerDebugBox()"
+							>
+								<i class="material-icons icon-with-button">
+									bug_report
+								</i>
+								<span class="optional-desktop-only-text">
+									Toggle debug player box
+								</span>
+							</button>
+
+							<!-- (Admin) Skip Button -->
+							<button
+								class="button is-danger"
+								@click="skipStation()"
+							>
+								<i class="material-icons icon-with-button"
+									>skip_next</i
+								>
+								<span class="optional-desktop-only-text">
+									Force Skip
+								</span>
+							</button>
+
+							<!-- (Admin) Pause/Resume Button -->
+							<button
+								class="button is-danger"
+								v-if="stationPaused"
+								@click="resumeStation()"
+							>
+								<i class="material-icons icon-with-button"
+									>play_arrow</i
+								>
+								<span class="optional-desktop-only-text">
+									Resume Station
+								</span>
+							</button>
+							<button
+								class="button is-danger"
+								@click="pauseStation()"
+								v-else
+							>
+								<i class="material-icons icon-with-button"
+									>pause</i
+								>
+								<span class="optional-desktop-only-text">
+									Pause Station
+								</span>
+							</button>
+						</div>
+					</div>
+					<div id="currently-playing-container" class="quadrant">
+						<currently-playing v-if="!noSong" />
+						<p
+							v-else
+							class="nothing-here"
+							id="no-currently-playing"
 						>
-					</h4>
-					<h1
-						v-if="
-							station.type === 'community' &&
-								!station.partyMode &&
-								this.userId === station.owner &&
-								station.privatePlaylist
-						"
-					>
-						Maybe you can add some songs to your selected private
-						playlist and then press the skip button
-					</h1>
+							No song is currently playing
+						</p>
+					</div>
 				</div>
-				<div v-show="!noSong" class="columns">
-					<div
-						class="column is-8-desktop is-offset-2-desktop is-12-mobile"
-					>
-						<div class="video-container">
-							<div id="player" />
+				<div id="lower-row" class="row">
+					<div class="player-container quadrant" v-show="!noSong">
+						<div id="video-container">
+							<div
+								id="player"
+								style="width: 100%; height: 100%"
+							/>
 							<div
-								class="player-can-not-autoplay"
+								class="player-cannot-autoplay"
 								v-if="!canAutoplay"
 							>
 								<p>
@@ -83,328 +123,148 @@
 								</p>
 							</div>
 						</div>
-						<div
-							id="preview-progress"
-							class="seeker-bar-container white"
-						>
-							<div
-								class="seeker-bar light-blue"
-								style="width: 0%"
-							/>
-						</div>
-					</div>
-					<div
-						class="desktop-only column is-3-desktop card playlistCard experimental"
-					>
-						<div v-if="station.type === 'community'" class="title">
-							Queue
+						<div id="seeker-bar-container">
+							<div id="seeker-bar" style="width: 0%" />
 						</div>
-						<div v-else class="title">
-							Playlist
-						</div>
-						<article v-if="!noSong" class="media">
-							<figure class="media-left">
-								<p class="image is-64x64">
-									<img
-										:src="currentSong.thumbnail"
-										onerror="this.src='/assets/notes-transparent.png'"
-									/>
-								</p>
-							</figure>
-							<div class="media-content">
-								<div class="content">
-									<p>
-										Current Song:
-										<br />
-										<strong>{{ currentSong.title }}</strong>
-										<br />
-										<small>{{ currentSong.artists }}</small>
-									</p>
-								</div>
-							</div>
-							<div class="media-right">
-								{{ utils.formatTime(currentSong.duration) }}
-							</div>
-						</article>
-						<p v-if="noSong" class="has-text-centered">
-							There is currently no song playing.
-						</p>
-
-						<article
-							v-for="(song, index) in songsList"
-							:key="index"
-							class="media"
-						>
-							<div class="media-content">
-								<div class="content">
-									<strong class="songTitle">{{
-										song.title
-									}}</strong>
-									<br />
-									<small>{{ song.artists.join(", ") }}</small>
-									<br />
-									<div v-if="station.partyMode">
-										<br />
-										<small>
-											Requested by
-											<b>
-												<user-id-to-username
-													:user-id="song.requestedBy"
-													:link="true"
-												/>
-											</b>
-										</small>
-										<button
-											v-if="
-												isOwnerOnly() || isAdminOnly()
-											"
-											class="button"
-											@click="
-												removeFromQueue(song.songId)
-											"
-										>
-											REMOVE
-										</button>
-									</div>
-								</div>
-							</div>
-							<div class="media-right">
-								{{ utils.formatTime(song.duration) }}
+						<div id="control-bar-container">
+							<div id="left-buttons">
+								<!-- Local Pause/Resume Button -->
+								<button
+									class="button is-primary"
+									@click="resumeLocalStation()"
+									id="local-resume"
+									v-if="localPaused"
+								>
+									<i class="material-icons">play_arrow</i>
+									<span class="optional-desktop-only-text"
+										>Play locally</span
+									>
+								</button>
+								<button
+									class="button is-primary"
+									@click="pauseLocalStation()"
+									id="local-pause"
+									v-else
+								>
+									<i class="material-icons">pause</i>
+									<span class="optional-desktop-only-text"
+										>Pause locally</span
+									>
+								</button>
+
+								<!-- Vote to Skip Button -->
+								<button
+									class="button is-primary"
+									@click="voteSkipStation()"
+								>
+									<i class="material-icons icon-with-button"
+										>skip_next</i
+									>
+									<span class="optional-desktop-only-text"
+										>Vote to skip (</span
+									>
+									{{ currentSong.skipVotes }}
+									<span class="optional-desktop-only-text"
+										>)</span
+									>
+								</button>
 							</div>
-						</article>
-						<a
-							v-if="station.type === 'community' && loggedIn"
-							class="button add-to-queue"
-							href="#"
-							@click="
-								openModal({
-									sector: 'station',
-									modal: 'addSongToQueue'
-								})
-							"
-							>Add a song to the queue</a
-						>
-					</div>
-				</div>
-				<div v-show="!noSong" class="desktop-only columns is-mobile">
-					<div
-						class="column is-8-desktop is-offset-2-desktop is-12-mobile"
-					>
-						<div class="columns is-mobile">
-							<div class="column is-12-desktop">
-								<h4 id="time-display">
+							<div id="duration">
+								<p>
 									{{ timeElapsed }} /
 									{{ utils.formatTime(currentSong.duration) }}
-								</h4>
-								<h3>{{ currentSong.title }}</h3>
-								<h4 class="thin" style="margin-left: 0">
-									{{ currentSong.artists }}
-								</h4>
-								<div class="columns is-mobile">
-									<form
-										style="margin-top: 12px; margin-bottom: 0"
-										action="#"
-										class="column is-7-desktop is-4-mobile"
+								</p>
+							</div>
+							<p id="volume-control">
+								<i
+									v-if="muted"
+									class="material-icons"
+									@click="toggleMute()"
+									>volume_mute</i
+								>
+								<i
+									v-else
+									class="material-icons"
+									@click="toggleMute()"
+									>volume_down</i
+								>
+								<input
+									v-model="volumeSliderValue"
+									type="range"
+									min="0"
+									max="10000"
+									class="volume-slider active"
+									@change="changeVolume()"
+									@input="changeVolume()"
+								/>
+								<i
+									class="material-icons"
+									@click="increaseVolume()"
+									>volume_up</i
+								>
+							</p>
+							<div id="right-buttons">
+								<!-- Ratings (Like/Dislike) Buttons -->
+								<div
+									id="ratings"
+									v-if="
+										true
+										//currentSong.likes !== -1 && currentSong.dislikes !== -1
+									"
+								>
+									<!-- Like Song Button -->
+									<button
+										class="button is-success"
+										id="like-song"
+										@click="toggleLike()"
 									>
-										<p class="volume-slider-wrapper">
-											<i
-												v-if="muted"
-												class="material-icons"
-												@click="toggleMute()"
-												>volume_mute</i
-											>
-											<i
-												v-else
-												class="material-icons"
-												@click="toggleMute()"
-												>volume_down</i
-											>
-											<input
-												v-model="volumeSliderValue"
-												type="range"
-												min="0"
-												max="10000"
-												class="volumeSlider active"
-												@change="changeVolume()"
-												@input="changeVolume()"
-											/>
-											<i
-												class="material-icons"
-												@click="increaseVolume()"
-												>volume_up</i
-											>
-										</p>
-									</form>
-									<div
-										class="column is-8-mobile is-5-desktop"
+										<i
+											class="material-icons icon-with-button"
+											:class="{ liked: liked }"
+											>thumb_up_alt</i
+										>{{ currentSong.likes }}
+									</button>
+
+									<!-- Dislike Song Button -->
+									<button
+										class="button is-danger"
+										id="dislike-song"
+										@click="toggleDislike()"
 									>
-										<ul
-											v-if="
-												currentSong.likes !== -1 &&
-													currentSong.dislikes !== -1
-											"
-											id="ratings"
-										>
-											<li
-												id="like"
-												style="margin-right: 10px"
-												@click="toggleLike()"
-											>
-												<span class="flow-text">{{
-													currentSong.likes
-												}}</span>
-												<i
-													id="thumbs_up"
-													class="material-icons grey-text"
-													:class="{ liked: liked }"
-													>thumb_up</i
-												>
-												<a
-													class="absolute-a behind"
-													href="#"
-													@click="toggleLike()"
-												/>
-											</li>
-											<li
-												id="dislike"
-												@click="toggleDislike()"
-											>
-												<span class="flow-text">{{
-													currentSong.dislikes
-												}}</span>
-												<i
-													id="thumbs_down"
-													class="material-icons grey-text"
-													:class="{
-														disliked: disliked
-													}"
-													>thumb_down</i
-												>
-												<a
-													class="absolute-a behind"
-													href="#"
-													@click="toggleDislike()"
-												/>
-											</li>
-										</ul>
-									</div>
+										<i
+											class="material-icons icon-with-button"
+											:class="{
+												disliked: disliked
+											}"
+											>thumb_down_alt</i
+										>{{ currentSong.dislikes }}
+									</button>
 								</div>
-							</div>
-							<div
-								v-if="!currentSong.simpleSong"
-								class="column is-3-desktop experimental"
-							>
-								<img
-									class="image"
-									:src="currentSong.thumbnail"
-									alt="Song Thumbnail"
-									onerror="this.src='/assets/notes-transparent.png'"
-								/>
+
+								<!-- Add Song To Playlist Button (will be dropdown soon) -->
+								<button
+									class="button is-primary"
+									id="add-song-to-playlist"
+									@click="
+										openModal({
+											sector: 'station',
+											modal: 'addSongToPlaylist'
+										})
+									"
+								>
+									<i class="material-icons">queue</i>
+									<span class="optional-desktop-only-text"
+										>Add Song To Playlist</span
+									>
+								</button>
 							</div>
 						</div>
 					</div>
-				</div>
-				<div v-show="!noSong" class="mobile-only">
-					<div>
-						<div>
-							<div>
-								<h3>{{ currentSong.title }}</h3>
-								<h4 class="thin">
-									{{ currentSong.artists }}
-								</h4>
-								<h5>
-									{{ timeElapsed }} /
-									{{ utils.formatTime(currentSong.duration) }}
-								</h5>
-								<div>
-									<form class="columns" action="#">
-										<p
-											class="column is-11-mobile volume-slider-wrapper"
-										>
-											<i
-												v-if="muted"
-												class="material-icons"
-												@click="toggleMute()"
-												>volume_mute</i
-											>
-											<i
-												v-else
-												class="material-icons"
-												@click="toggleMute()"
-												>volume_down</i
-											>
-											<input
-												v-model="volumeSliderValue"
-												type="range"
-												min="0"
-												max="10000"
-												class="active volumeSlider"
-												@change="changeVolume()"
-												@input="changeVolume()"
-											/>
-											<i
-												class="material-icons"
-												@click="increaseVolume()"
-												>volume_up</i
-											>
-										</p>
-									</form>
-									<div>
-										<ul
-											v-if="
-												currentSong.likes !== -1 &&
-													currentSong.dislikes !== -1
-											"
-											id="ratings"
-											style="display: inline-block"
-										>
-											<li
-												id="dislike"
-												style="display: inline-block;margin-right: 10px;"
-												@click="toggleDislike()"
-											>
-												<span class="flow-text">{{
-													currentSong.dislikes
-												}}</span>
-												<i
-													id="thumbs_down"
-													class="material-icons grey-text"
-													:class="{
-														disliked: disliked
-													}"
-													>thumb_down</i
-												>
-												<a
-													class="absolute-a behind"
-													href="#"
-													@click="toggleDislike()"
-												/>
-											</li>
-											<li
-												id="like"
-												style="display: inline-block"
-												@click="toggleLike()"
-											>
-												<span class="flow-text">{{
-													currentSong.likes
-												}}</span>
-												<i
-													id="thumbs_up"
-													class="material-icons grey-text"
-													:class="{ liked: liked }"
-													>thumb_up</i
-												>
-												<a
-													class="absolute-a behind"
-													href="#"
-													@click="toggleLike()"
-												/>
-											</li>
-										</ul>
-									</div>
-								</div>
-							</div>
-						</div>
+					<p class="player-container nothing-here" v-if="noSong">
+						No song is currently playing
+					</p>
+					<div id="sidebar-container" class="quadrant">
+						<station-sidebar />
 					</div>
 				</div>
 			</div>
@@ -415,17 +275,6 @@
 			<create-playlist v-if="modals.createPlaylist" />
 			<edit-station v-if="modals.editStation" store="station" />
 			<report v-if="modals.report" />
-
-			<transition name="slide-outer">
-				<div class="sidebar-container" v-if="sidebarActive">
-					<transition name="slide-inner">
-						<songs-list-sidebar v-if="sidebars.songslist" />
-					</transition>
-					<transition name="slide-inner">
-						<users-sidebar v-if="sidebars.users" />
-					</transition>
-				</div>
-			</transition>
 		</div>
 
 		<floating-box id="playerDebugBox" ref="playerDebugBox">
@@ -472,9 +321,8 @@
 import { mapState, mapActions } from "vuex";
 import Toast from "toasters";
 
-import StationHeader from "./StationHeader.vue";
+import MainHeader from "../../components/layout/MainHeader.vue";
 
-import UserIdToUsername from "../../components/common/UserIdToUsername.vue";
 import Z404 from "../404.vue";
 
 import FloatingBox from "../../components/ui/FloatingBox.vue";
@@ -483,9 +331,12 @@ import io from "../../io";
 import keyboardShortcuts from "../../keyboardShortcuts";
 import utils from "../../../js/utils";
 
+import CurrentlyPlaying from "./components/CurrentlyPlaying.vue";
+import StationSidebar from "./components/Sidebar/index.vue";
+
 export default {
 	components: {
-		StationHeader,
+		MainHeader,
 		SongQueue: () => import("./AddSongToQueue.vue"),
 		AddToPlaylist: () => import("./AddSongToPlaylist.vue"),
 		EditPlaylist: () => import("../../components/modals/EditPlaylist.vue"),
@@ -493,11 +344,10 @@ export default {
 			import("../../components/modals/CreatePlaylist.vue"),
 		EditStation: () => import("../../components/modals/EditStation.vue"),
 		Report: () => import("./Report.vue"),
-		SongsListSidebar: () => import("./SongsList.vue"),
-		UsersSidebar: () => import("./UsersList.vue"),
-		UserIdToUsername,
 		Z404,
-		FloatingBox
+		FloatingBox,
+		CurrentlyPlaying,
+		StationSidebar
 	},
 	data() {
 		return {
@@ -529,9 +379,6 @@ export default {
 		...mapState("modals", {
 			modals: state => state.modals.station
 		}),
-		...mapState("sidebars", {
-			sidebars: state => state.sidebars.station
-		}),
 		...mapState("station", {
 			station: state => state.station,
 			currentSong: state => state.currentSong,
@@ -546,10 +393,7 @@ export default {
 			loggedIn: state => state.user.auth.loggedIn,
 			userId: state => state.user.auth.userId,
 			role: state => state.user.auth.role
-		}),
-		sidebarActive() {
-			return Object.values(this.sidebars).indexOf(true) !== -1;
-		}
+		})
 	},
 	mounted() {
 		Date.currently = () => {
@@ -576,14 +420,21 @@ export default {
 				const previousSong = this.currentSong.songId
 					? this.currentSong
 					: null;
+
 				this.updatePreviousSong(previousSong);
-				this.updateCurrentSong(
-					data.currentSong ? data.currentSong : {}
-				);
+
+				const { currentSong } = data;
+
+				if (currentSong && !currentSong.thumbnail)
+					currentSong.ytThumbnail = `https://img.youtube.com/vi/${currentSong.songId}/mqdefault.jpg`;
+
+				this.updateCurrentSong(currentSong || {});
+
 				this.startedAt = data.startedAt;
 				this.updateStationPaused(data.paused);
 				this.timePaused = data.timePaused;
-				if (data.currentSong) {
+
+				if (currentSong) {
 					this.updateNoSong(false);
 					if (this.currentSong.artists)
 						this.currentSong.artists = this.currentSong.artists.join(
@@ -761,6 +612,22 @@ export default {
 		isOwnerOrAdmin() {
 			return this.isOwnerOnly() || this.isAdminOnly();
 		},
+		openSettings() {
+			this.editStation({
+				_id: this.station._id,
+				name: this.station.name,
+				type: this.station.type,
+				partyMode: this.station.partyMode,
+				description: this.station.description,
+				privacy: this.station.privacy,
+				displayName: this.station.displayName,
+				locked: this.station.locked
+			});
+			this.openModal({
+				sector: "station",
+				modal: "editStation"
+			});
+		},
 		removeFromQueue(songId) {
 			window.socket.emit(
 				"stations.removeFromQueue",
@@ -891,9 +758,9 @@ export default {
 		},
 		resizeSeekerbar() {
 			if (!this.stationPaused) {
-				document.getElementsByClassName(
+				document.getElementById(
 					"seeker-bar"
-				)[0].style.width = `${parseFloat(
+				).style.width = `${parseFloat(
 					(this.getTimeElapsed() / 1000 / this.currentSong.duration) *
 						100
 				)}%`;
@@ -1272,8 +1139,6 @@ export default {
 		},
 		join() {
 			this.socket.emit("stations.join", this.stationName, res => {
-				console.log(res.data);
-
 				if (res.status === "success") {
 					this.loading = false;
 
@@ -1301,12 +1166,19 @@ export default {
 						privatePlaylist,
 						type
 					});
+
 					const currentSong = res.data.currentSong
 						? res.data.currentSong
 						: {};
+
 					if (currentSong.artists)
 						currentSong.artists = currentSong.artists.join(", ");
+
+					if (currentSong && !currentSong.thumbnail)
+						currentSong.ytThumbnail = `https://img.youtube.com/vi/${currentSong.songId}/mqdefault.jpg`;
+
 					this.updateCurrentSong(currentSong);
+
 					this.startedAt = res.data.startedAt;
 					this.updateStationPaused(res.data.paused);
 					this.timePaused = res.data.timePaused;
@@ -1447,7 +1319,6 @@ export default {
 				}
 			});
 		},
-		...mapActions("sidebars", ["toggleSidebar"]),
 		...mapActions("modals", ["openModal"]),
 		...mapActions("station", [
 			"joinStation",
@@ -1458,603 +1329,485 @@ export default {
 			"updateSongsList",
 			"updateStationPaused",
 			"updateLocalPaused",
-			"updateNoSong"
+			"updateNoSong",
+			"editStation"
 		])
 	}
 };
 </script>
 
-<style lang="scss">
+<style lang="scss" scoped>
 @import "../../styles/global.scss";
 
-.station-parent {
-	display: flex;
-	flex: 1;
-	overflow-x: hidden;
+.progress {
+	width: 50px;
+	animation: rotate 0.8s infinite linear;
+	border: 8px solid $primary-color;
+	border-right-color: transparent;
+	height: 50px;
+	position: absolute;
+	top: 50%;
+	left: 50%;
 }
 
-.night-mode {
-	.nav,
-	.control-sidebar {
-		background-color: #222;
+@keyframes rotate {
+	0% {
+		transform: rotate(0deg);
 	}
-}
-
-.player-can-not-autoplay {
-	position: absolute;
-	width: 100%;
-	height: 100%;
-	background: rgba(3, 169, 244, 0.95);
-	display: flex;
-	align-items: center;
-	justify-content: center;
-
-	p {
-		color: $white;
-		font-size: 26px;
-		text-align: center;
+	100% {
+		transform: rotate(360deg);
 	}
 }
 
-// Starting state for enter. Added before element is inserted, removed one frame after element is inserted.
-.slide-outer-enter {
-	margin-right: -300px;
-}
-
-// Active state for enter. Applied during the entire entering phase. Added before element is inserted, removed when transition/animation finishes. This class can be used to define the duration, delay and easing curve for the entering transition.
-.slide-outer-enter-active {
-	transition: all 0.3s linear;
-}
-
-// Only available in versions 2.1.8+. Ending state for enter. Added one frame after element is inserted (at the same time v-enter is removed), removed when transition/animation finishes.
-.slide-outer-enter-to {
-	margin-right: 0;
-}
-
-// Starting state for leave. Added immediately when a leaving transition is triggered, removed after one frame.
-.slide-outer-leave {
-	margin-right: 0;
-}
-
-// Active state for leave. Applied during the entire leaving phase. Added immediately when leave transition is triggered, removed when the transition/animation finishes. This class can be used to define the duration, delay and easing curve for the leaving transition.
-.slide-outer-leave-active {
-	transition: all 0.3s linear;
-}
-
-// Only available in versions 2.1.8+. Ending state for leave. Added one frame after a leaving transition is triggered (at the same time v-leave is removed), removed when the transition/animation finishes.
-.slide-outer-leave-to {
-	margin-right: -300px;
-}
-
-// Starting state for enter. Added before element is inserted, removed one frame after element is inserted.
-.slide-inner-enter {
-	transform: translateX(300px);
-}
-
-// Active state for enter. Applied during the entire entering phase. Added before element is inserted, removed when transition/animation finishes. This class can be used to define the duration, delay and easing curve for the entering transition.
-.slide-inner-enter-active {
-	transition: all 0.3s linear;
-	z-index: 5;
-}
-
-// Only available in versions 2.1.8+. Ending state for enter. Added one frame after element is inserted (at the same time v-enter is removed), removed when transition/animation finishes.
-.slide-inner-enter-to {
-	transform: translateX(0px);
-}
-
-// Starting state for leave. Added immediately when a leaving transition is triggered, removed after one frame.
-.slide-inner-leave {
-	transform: translateX(0px);
-}
-
-// Active state for leave. Applied during the entire leaving phase. Added immediately when leave transition is triggered, removed when the transition/animation finishes. This class can be used to define the duration, delay and easing curve for the leaving transition.
-.slide-inner-leave-active {
-	transition: all 0.3s linear;
-	z-index: 0;
-}
-
-// Only available in versions 2.1.8+. Ending state for leave. Added one frame after a leaving transition is triggered (at the same time v-leave is removed), removed when the transition/animation finishes.
-.slide-inner-leave-to {
-	transform: translateX(300px);
-}
-
-.sidebar-container {
-	width: 300px;
-	max-width: 300px;
-	// background-color: blue;
-	position: relative;
-}
-
-.sidebar {
-	position: absolute;
-	// z-index: 1;
-	top: 0;
-	right: 0;
-	width: 300px;
-	height: 100%;
-	background-color: $white;
-	box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16),
-		0 2px 10px 0 rgba(0, 0, 0, 0.12);
-}
-
-.no-song {
-	color: $primary-color;
-	text-align: center;
+.experimental {
+	display: none !important;
 }
 
-.volumeSlider {
-	padding: 0 15px;
-	background: transparent;
-}
+#playerDebugBox {
+	.box-body {
+		flex-direction: column;
 
-.volume-slider-wrapper {
-	margin-top: 0;
-	position: relative;
-	display: flex;
-	align-items: center;
-	.material-icons {
-		user-select: none;
+		b {
+			color: #000;
+		}
 	}
 }
 
-.material-icons {
-	cursor: pointer;
-}
+.night-mode {
+	#currently-playing-container,
+	#about-station-container,
+	#control-bar-container {
+		background-color: #222 !important;
+	}
 
-.stationDisplayName {
-	color: $white !important;
+	#upper-row .quadrant,
+	#video-container,
+	#control-bar-container {
+		border: 0 !important;
+	}
 }
 
-.add-to-playlist {
-	display: flex;
-	align-items: center;
-	justify-content: center;
-}
+/** Ultrawide */
 
-// .slideout {
-// 	top: 50px;
-// 	height: 100%;
-// 	position: fixed;
-// 	right: 0;
-// 	width: 350px;
-// 	background-color: $white;
-// 	box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16),
-// 		0 2px 10px 0 rgba(0, 0, 0, 0.12);
-// 	.slideout-header {
-// 		text-align: center;
-// 		background-color: rgb(3, 169, 244) !important;
-// 		margin: 0;
-// 		padding-top: 5px;
-// 		padding-bottom: 7px;
-// 		color: $white;
-// 	}
-
-// 	.slideout-content {
-// 		height: 100%;
-// 	}
-// }
-
-.modal-large {
-	width: 75%;
+@media (min-height: 1200px) {
+	#station-inner-container {
+		transform: scale(1.1);
+	}
 }
 
-.station {
-	padding-top: 0.5vw;
-	transition: all 0.1s;
+#station-outer-container {
 	margin: 0 auto;
-	flex: 0.9;
-
-	@media only screen and (min-width: 993px) {
-		flex: 0.7;
-	}
+	max-width: 100%;
+	padding: 0 40px;
+	margin-top: 5px;
 
-	@media only screen and (min-width: 601px) {
-		flex: 0.85;
-	}
+	@media (max-width: 1040px) {
+		padding: 0;
+		margin-top: 0 !important;
 
-	@media (min-width: 999px) {
-		.mobile-only {
-			display: none;
-		}
-		.desktop-only {
-			display: block;
-		}
-	}
-	@media (max-width: 998px) {
-		.mobile-only {
-			display: block;
-		}
-		.desktop-only {
-			display: none;
-			visibility: hidden;
+		#station-inner-container {
+			justify-content: flex-start !important;
 		}
-	}
 
-	.mobile-only {
-		text-align: center;
-	}
+		.row {
+			width: 100%;
+			flex-direction: column !important;
+		}
 
-	.playlistCard {
-		margin: 10px;
-		position: relative;
-		padding-bottom: calc(31.25% + 7px);
-		height: 0;
-		overflow-y: scroll;
-
-		.title {
-			background-color: rgb(3, 169, 244);
-			text-align: center;
-			padding: 10px;
-			color: $white;
-			font-weight: 600;
+		.quadrant,
+		.player-container {
+			width: 100% !important;
+			border: 0 !important;
+			background: transparent !important;
 		}
 
-		.media {
-			padding: 0 25px;
+		#lower-row {
+			margin-top: 0 !important;
 		}
 
-		.media-content .content {
-			min-height: 64px;
-			max-height: 64px;
-			display: flex;
-			align-items: center;
+		.player-container {
+			height: 450px !important;
 		}
 
-		.content p strong {
-			word-break: break-word;
+		.player-container.nothing-here {
+			height: 50px !important;
 		}
 
-		.content p small {
-			word-break: break-word;
+		#no-currently-playing {
+			display: none;
 		}
 
-		.add-to-queue {
-			width: 100%;
-			margin-top: 25px;
-			height: 40px;
-			border-radius: 0;
-			background: rgb(3, 169, 244);
-			color: $white !important;
-			border: 0;
-			&:active,
-			&:focus {
-				border: 0;
+		#control-bar-container {
+			#left-buttons,
+			#duration,
+			#volume-control,
+			#right-buttons {
+				margin: 3px;
 			}
 		}
+	}
 
-		.add-to-queue:focus {
-			background: $primary-color;
-		}
+	#station-inner-container {
+		display: flex;
+		align-items: center;
+		flex-direction: column;
 
-		.media-right {
-			line-height: 64px;
+		@media (min-height: 1050px) {
+			justify-content: center;
+			height: calc(100vh - 64px - 5px);
 		}
 
-		.songTitle {
-			word-wrap: break-word;
-			overflow: hidden;
-			text-overflow: ellipsis;
-			display: -webkit-box;
-			-webkit-box-orient: vertical;
-			-webkit-line-clamp: 2;
-			line-height: 20px;
-			max-height: 40px;
-			width: 100%;
+		@media (min-width: 1040px) {
+			#currently-playing-container,
+			#sidebar-container {
+				margin-left: 75px;
+			}
 		}
-	}
-
-	input[type="range"] {
-		-webkit-appearance: none;
-		width: 100%;
-		margin: 7.3px 0;
-	}
-
-	input[type="range"]:focus {
-		outline: none;
-	}
-
-	input[type="range"]::-webkit-slider-runnable-track {
-		width: 100%;
-		height: 5.2px;
-		cursor: pointer;
-		box-shadow: 0;
-		background: $light-grey-2;
-		border-radius: 0;
-		border: 0;
-	}
-
-	input[type="range"]::-webkit-slider-thumb {
-		box-shadow: 0;
-		border: 0;
-		height: 19px;
-		width: 19px;
-		border-radius: 15px;
-		background: $primary-color;
-		cursor: pointer;
-		-webkit-appearance: none;
-		margin-top: -6.5px;
-	}
-
-	input[type="range"]::-moz-range-track {
-		width: 100%;
-		height: 5.2px;
-		cursor: pointer;
-		box-shadow: 0;
-		background: $light-grey-2;
-		border-radius: 0;
-		border: 0;
-	}
-
-	input[type="range"]::-moz-range-thumb {
-		box-shadow: 0;
-		border: 0;
-		height: 19px;
-		width: 19px;
-		border-radius: 15px;
-		background: $primary-color;
-		cursor: pointer;
-		-webkit-appearance: none;
-		margin-top: -6.5px;
-	}
 
-	input[type="range"]::-ms-track {
-		width: 100%;
-		height: 5.2px;
-		cursor: pointer;
-		box-shadow: 0;
-		background: $light-grey-2;
-		border-radius: 1.3px;
-	}
+		@media (min-width: 1040px) and (max-width: 1450px) {
+			#about-station-container,
+			.player-container {
+				width: 800px !important;
+			}
+		}
 
-	input[type="range"]::-ms-fill-lower {
-		background: $light-grey-2;
-		border: 0;
-		border-radius: 0;
-		box-shadow: 0;
-	}
+		@media (min-width: 1450px) and (max-width: 2200px) {
+			#about-station-container,
+			.player-container {
+				width: 1400px !important;
+			}
+		}
 
-	input[type="range"]::-ms-fill-upper {
-		background: $light-grey-2;
-		border: 0;
-		border-radius: 0;
-		box-shadow: 0;
-	}
+		@media (min-width: 1040px) and (max-width: 1950px) {
+			#control-bar-container {
+				.optional-desktop-only-text {
+					display: none;
+				}
 
-	input[type="range"]::-ms-thumb {
-		box-shadow: 0;
-		border: 0;
-		height: 15px;
-		width: 15px;
-		border-radius: 15px;
-		background: $primary-color;
-		cursor: pointer;
-		-webkit-appearance: none;
-		margin-top: 1.5px;
-	}
+				.button {
+					width: 75px;
+				}
 
-	.video-container {
-		position: relative;
-		padding-bottom: 56.25%;
-		height: 0;
-		overflow: hidden;
+				#add-song-to-playlist,
+				#local-resume,
+				#local-pause {
+					i {
+						margin-right: 0 !important;
+					}
+				}
+			}
 
-		iframe {
-			position: absolute;
-			top: 0;
-			left: 0;
-			width: 100%;
-			height: 100%;
+			#currently-playing-container,
+			#sidebar-container {
+				margin-left: 40px;
+			}
 		}
-	}
-	.video-col {
-		padding-right: 0.75rem;
-		padding-left: 0.75rem;
-	}
-}
 
-.room-title {
-	left: 50%;
-	-webkit-transform: translateX(-50%);
-	transform: translateX(-50%);
-	font-size: 2.1em;
-}
+		@media (min-width: 1950px) {
+			#currently-playing-container,
+			#sidebar-container {
+				margin-left: 100px;
+			}
+		}
 
-#ratings {
-	display: flex;
-	justify-content: flex-end;
+		.row {
+			display: flex;
+			flex-direction: row;
+			max-width: 100%;
+
+			.quadrant {
+				display: flex;
+				align-items: center;
+				justify-content: center;
+				flex-direction: column;
+				max-width: 100%;
+				border-radius: 5px;
+			}
+		}
 
-	span {
-		font-size: 1.68rem;
-	}
+		#upper-row {
+			.quadrant {
+				border: 1px solid $light-grey-2;
+				background-color: #fff;
+			}
 
-	i {
-		color: #9e9e9e !important;
-		cursor: pointer;
-		transition: 0.1s color;
-	}
-}
+			#about-station-container {
+				width: 1400px;
+				align-items: flex-start;
+				padding: 20px;
 
-#time-display {
-	margin-top: 30px;
-	float: right;
-}
+				#station-name {
+					flex-direction: row !important;
 
-#thumbs_up:hover,
-#thumbs_up.liked {
-	color: $green !important;
-}
+					h1 {
+						margin: 0;
+						font-size: 36px;
+					}
 
-#thumbs_down:hover,
-#thumbs_down.disliked {
-	color: $red !important;
-}
+					i {
+						margin-left: 10px;
+						font-size: 30px;
+						color: $yellow;
+					}
+				}
 
-#song-thumbnail {
-	max-width: 100%;
-	width: 85%;
-}
+				p {
+					font-size: 14px;
+					max-width: 700px;
+				}
 
-.seeker-bar-container {
-	position: relative;
-	height: 7px;
-	display: block;
-	width: 100%;
-	overflow: hidden;
-}
+				#admin-buttons {
+					margin-top: 15px;
 
-.seeker-bar {
-	top: 0;
-	left: 0;
-	bottom: 0;
-	position: absolute;
-}
+					@media (max-width: 650px) {
+						.optional-desktop-only-text {
+							display: none;
+						}
+					}
+				}
+			}
 
-ul {
-	list-style: none;
-	margin: 0;
-	display: block;
-}
+			#currently-playing-container {
+				width: 550px;
+			}
+		}
 
-h1,
-h2,
-h3,
-h4,
-h5,
-h6 {
-	font-weight: 400;
-	line-height: 1.1;
-}
+		#lower-row {
+			margin-top: 20px;
+
+			.player-container {
+				background-color: #fff;
+				width: 1400px;
+				height: 770px;
+				display: flex;
+				flex-direction: column;
+
+				#video-container {
+					width: 100%;
+					height: 100%;
+					border: 1px solid $light-grey-2;
+					border-bottom: 0;
+
+					.player-cannot-autoplay {
+						position: absolute;
+						width: 100%;
+						height: 100%;
+						background: rgba(3, 169, 244, 0.95);
+						display: flex;
+						align-items: center;
+						justify-content: center;
+
+						p {
+							color: $white;
+							font-size: 26px;
+							text-align: center;
+						}
+					}
+				}
 
-h1 a,
-h2 a,
-h3 a,
-h4 a,
-h5 a,
-h6 a {
-	font-weight: inherit;
-}
+				#seeker-bar-container {
+					background-color: #fff;
+					position: relative;
+					height: 7px;
+					display: block;
+					width: 100%;
+					overflow: hidden;
+
+					#seeker-bar {
+						background-color: $musare-blue;
+						top: 0;
+						left: 0;
+						bottom: 0;
+						position: absolute;
+					}
+				}
 
-h1 {
-	font-size: 4.2rem;
-	line-height: 110%;
-	margin: 2.1rem 0 1.68rem 0;
-}
+				#control-bar-container {
+					display: flex;
+					justify-content: space-around;
+					padding: 10px 0;
+					width: 100%;
+					background: #fff;
+					border: 1px solid $light-grey-2;
+					border-radius: 0 0 5px 5px;
+					border-top: 0;
+
+					@media (max-width: 1450px) {
+						flex-direction: column;
+						flex-flow: wrap;
+
+						#right-buttons {
+							order: -1;
+						}
+					}
 
-h2 {
-	font-size: 3.56rem;
-	line-height: 110%;
-	margin: 1.78rem 0 1.424rem 0;
-}
+					#left-buttons,
+					#right-buttons {
+						i {
+							margin-right: 3px;
+						}
+					}
 
-h3 {
-	font-size: 2.92rem;
-	line-height: 110%;
-	margin: 1.46rem 0 1.168rem 0;
-}
+					#left-buttons {
+						display: flex;
 
-h4 {
-	font-size: 2.28rem;
-	line-height: 110%;
-	margin: 1.14rem 0 0.912rem 0;
-}
+						.button:not(:first-of-type) {
+							margin-left: 5px;
+						}
+					}
 
-h5 {
-	font-size: 1.64rem;
-	line-height: 110%;
-	margin: 0.82rem 0 0.656rem 0;
-}
+					#duration {
+						display: flex;
+						align-items: center;
 
-h6 {
-	font-size: 1rem;
-	line-height: 110%;
-	margin: 0.5rem 0 0.4rem 0;
-}
+						p {
+							font-size: 22px;
+						}
+					}
 
-.thin {
-	font-weight: 200;
-}
+					#volume-control {
+						margin-top: 0;
+						position: relative;
+						display: flex;
+						align-items: center;
+						cursor: pointer;
 
-.light-blue {
-	background-color: $primary-color !important;
-}
+						.volume-slider {
+							width: 500px;
+							padding: 0 15px;
+							background: transparent;
 
-.white {
-	background-color: $white !important;
-}
+							@media (max-width: 1650px) {
+								width: 250px !important;
+							}
+						}
 
-.btn-search {
-	font-size: 14px;
-}
+						input[type="range"] {
+							-webkit-appearance: none;
+							margin: 7.3px 0;
+						}
 
-.menu {
-	padding: 0 10px;
-}
+						input[type="range"]:focus {
+							outline: none;
+						}
 
-.menu-list li a:hover {
-	color: #000 !important;
-}
+						input[type="range"]::-webkit-slider-runnable-track {
+							width: 100%;
+							height: 5.2px;
+							cursor: pointer;
+							box-shadow: 0;
+							background: $light-grey-2;
+							border-radius: 0;
+							border: 0;
+						}
 
-.menu-list li {
-	display: flex;
-	justify-content: space-between;
-}
+						input[type="range"]::-webkit-slider-thumb {
+							box-shadow: 0;
+							border: 0;
+							height: 19px;
+							width: 19px;
+							border-radius: 15px;
+							background: $primary-color;
+							cursor: pointer;
+							-webkit-appearance: none;
+							margin-top: -6.5px;
+						}
 
-.menu-list a {
-	/*padding: 0 10px !important;*/
-}
+						input[type="range"]::-moz-range-track {
+							width: 100%;
+							height: 5.2px;
+							cursor: pointer;
+							box-shadow: 0;
+							background: $light-grey-2;
+							border-radius: 0;
+							border: 0;
+						}
 
-.menu-list a:hover {
-	background-color: transparent;
-}
+						input[type="range"]::-moz-range-thumb {
+							box-shadow: 0;
+							border: 0;
+							height: 19px;
+							width: 19px;
+							border-radius: 15px;
+							background: $primary-color;
+							cursor: pointer;
+							-webkit-appearance: none;
+							margin-top: -6.5px;
+						}
+						input[type="range"]::-ms-track {
+							width: 100%;
+							height: 5.2px;
+							cursor: pointer;
+							box-shadow: 0;
+							background: $light-grey-2;
+							border-radius: 1.3px;
+						}
 
-.icons-group {
-	display: flex;
-}
+						input[type="range"]::-ms-fill-lower {
+							background: $light-grey-2;
+							border: 0;
+							border-radius: 0;
+							box-shadow: 0;
+						}
 
-#like,
-#dislike {
-	position: relative;
-}
+						input[type="range"]::-ms-fill-upper {
+							background: $light-grey-2;
+							border: 0;
+							border-radius: 0;
+							box-shadow: 0;
+						}
 
-.behind {
-	z-index: -1;
-}
+						input[type="range"]::-ms-thumb {
+							box-shadow: 0;
+							border: 0;
+							height: 15px;
+							width: 15px;
+							border-radius: 15px;
+							background: $primary-color;
+							cursor: pointer;
+							-webkit-appearance: none;
+							margin-top: 1.5px;
+						}
+					}
 
-.behind:focus {
-	z-index: 0;
-}
+					#right-buttons {
+						display: flex;
 
-.progress {
-	width: 50px;
-	animation: rotate 0.8s infinite linear;
-	border: 8px solid $primary-color;
-	border-right-color: transparent;
-	height: 50px;
-	position: absolute;
-	top: 50%;
-	left: 50%;
-}
+						#dislike-song,
+						#add-song-to-playlist {
+							margin-left: 5px;
+						}
 
-@keyframes rotate {
-	0% {
-		transform: rotate(0deg);
-	}
-	100% {
-		transform: rotate(360deg);
-	}
-}
+						#ratings {
+							display: flex;
 
-.experimental {
-	display: none !important;
-}
+							#like-song:hover,
+							#like-song.liked {
+								background-color: darken($green, 5%) !important;
+							}
 
-#playerDebugBox {
-	.box-body {
-		flex-direction: column;
+							#dislike-song:hover,
+							#dislike-song.disliked {
+								background-color: darken($red, 5%) !important;
+							}
+						}
+					}
+				}
+			}
 
-		b {
-			color: #000;
+			#sidebar-container {
+				position: relative;
+				width: 550px;
+			}
 		}
 	}
 }
+
+/deep/ .nothing-here {
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	height: inherit;
+	height: -webkit-fill-available;
+}
 </style>

+ 0 - 2
frontend/src/store/index.js

@@ -4,7 +4,6 @@ import Vuex from "vuex";
 import user from "./modules/user";
 import settings from "./modules/settings";
 import modals from "./modules/modals";
-import sidebars from "./modules/sidebars";
 import station from "./modules/station";
 import admin from "./modules/admin";
 
@@ -15,7 +14,6 @@ export default new Vuex.Store({
 		user,
 		settings,
 		modals,
-		sidebars,
 		station,
 		admin
 	},

+ 0 - 64
frontend/src/store/modules/sidebars.js

@@ -1,64 +0,0 @@
-/* eslint no-param-reassign: 0 */
-
-const state = {
-	sidebars: {
-		station: {
-			songslist: false,
-			users: false
-		}
-	},
-	currentlyActive: {}
-};
-
-const getters = {};
-
-const actions = {
-	toggleSidebar: ({ commit }, data) => {
-		commit("toggleSidebar", data);
-	},
-	openSidebar: ({ commit }, data) => {
-		commit("openSidebar", data);
-	},
-	closeCurrentSidebar: ({ commit }) => {
-		commit("closeCurrentSidebar");
-	}
-};
-
-const mutations = {
-	toggleSidebar(state, data) {
-		const { sector, sidebar } = data;
-
-		if (
-			state.currentlyActive.sidebar &&
-			state.currentlyActive.sidebar !== sidebar
-		) {
-			state.sidebars[state.currentlyActive.sector][
-				state.currentlyActive.sidebar
-			] = false;
-			state.currentlyActive = {};
-		}
-
-		state.sidebars[sector][sidebar] = !state.sidebars[sector][sidebar];
-
-		if (state.sidebars[sector][sidebar])
-			state.currentlyActive = { sector, sidebar };
-	},
-	openSidebar(state, data) {
-		const { sector, sidebar } = data;
-		state.sidebars[sector][sidebar] = true;
-		state.currentlyActive = { sector, sidebar };
-	},
-	closeCurrentSidebar(state) {
-		const { sector, sidebar } = state.currentlyActive;
-		state.sidebars[sector][sidebar] = false;
-		state.currentlyActive = {};
-	}
-};
-
-export default {
-	namespaced: true,
-	state,
-	getters,
-	actions,
-	mutations
-};