<template> <modal v-if="station" :title=" !isOwnerOrAdmin() && station.partyMode ? 'Add Song to Queue' : 'Manage Station' " :style="`--primary-color: var(--${station.theme})`" class="manage-station-modal" > <template #body> <div class="custom-modal-body" v-if="station && station._id"> <div class="left-section"> <div class="section"> <div id="about-station-container"> <div id="station-info"> <div id="station-name"> <h1>{{ station.displayName }}</h1> <i v-if="station.type === 'official'" class="material-icons verified-station" content="Verified Station" v-tippy > check_circle </i> <i class="material-icons stationMode" :content=" station.partyMode ? 'Station in Party mode' : 'Station in Playlist mode' " v-tippy >{{ station.partyMode ? "emoji_people" : "playlist_play" }}</i > </div> <p>{{ station.description }}</p> </div> <div id="admin-buttons" v-if="isOwnerOrAdmin()"> <!-- (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> Resume Station </span> </button> <button class="button is-danger" @click="pauseStation()" v-else > <i class="material-icons icon-with-button" >pause</i > <span> Pause Station </span> </button> <!-- (Admin) Skip Button --> <button class="button is-danger" @click="skipStation()" > <i class="material-icons icon-with-button" >skip_next</i > <span> Force Skip </span> </button> <!-- Station Settings Button --> <!-- <button class="button is-primary" @click="openModal('manageStation')" > <i class="material-icons icon-with-button" >settings</i > <span> Manage Station </span> </button> --> <router-link v-if="sector !== 'station' && station.name" :to="{ name: 'station', params: { id: station.name } }" class="button is-primary" > Go To Station </router-link> </div> </div> <div class="tab-selection"> <button v-if="isOwnerOrAdmin()" class="button is-default" :class="{ selected: tab === 'settings' }" ref="settings-tab" @click="showTab('settings')" > Settings </button> <button v-if="isAllowedToParty() || isOwnerOrAdmin()" class="button is-default" :class="{ selected: tab === 'playlists' }" ref="playlists-tab" @click="showTab('playlists')" > Playlists </button> <button v-if="isAllowedToParty() || isOwnerOrAdmin()" class="button is-default" :class="{ selected: tab === 'songs' }" ref="songs-tab" @click="showTab('songs')" > Songs </button> </div> <settings v-if="isOwnerOrAdmin()" class="tab" v-show="tab === 'settings'" /> <playlists v-if="isAllowedToParty() || isOwnerOrAdmin()" class="tab" v-show="tab === 'playlists'" /> <songs v-if="isAllowedToParty() || isOwnerOrAdmin()" class="tab" v-show="tab === 'songs'" /> </div> </div> <div class="right-section"> <div class="section"> <div class="queue-title"> <h4 class="section-title">Queue</h4> </div> <hr class="section-horizontal-rule" /> <song-item v-if="currentSong._id" :song="currentSong" :requested-by=" station.type === 'community' && station.partyMode === true " header="Currently Playing.." class="currently-playing" /> <queue sector="manageStation" /> </div> </div> </div> </template> <template #footer> <!-- <router-link v-if="sector !== 'station' && station.name" :to="{ name: 'station', params: { id: station.name } }" class="button is-primary" > Go To Station </router-link> --> <button class="button is-primary tab-actionable-button" v-if="loggedIn && station.type === 'official'" @click="openModal('requestSong')" > <i class="material-icons icon-with-button">queue</i> <span> Request Song </span> </button> <div v-if="isOwnerOrAdmin()" class="right"> <confirm @confirm="clearAndRefillStationQueue()"> <a class="button is-danger"> Clear and refill station queue </a> </confirm> <confirm v-if="station && station.type === 'community'" @confirm="removeStation()" > <button class="button is-danger">Delete station</button> </confirm> </div> </template> </modal> </template> <script> import { mapState, mapGetters, mapActions } from "vuex"; import Toast from "toasters"; import Confirm from "@/components/Confirm.vue"; import Queue from "@/components/Queue.vue"; import SongItem from "@/components/SongItem.vue"; import Modal from "../../Modal.vue"; import Settings from "./Tabs/Settings.vue"; import Playlists from "./Tabs/Playlists.vue"; import Songs from "./Tabs/Songs.vue"; export default { components: { Modal, Confirm, Queue, SongItem, Settings, Playlists, Songs }, props: { stationId: { type: String, default: "" }, sector: { type: String, default: "admin" } }, computed: { ...mapState({ loggedIn: state => state.user.auth.loggedIn, userId: state => state.user.auth.userId, role: state => state.user.auth.role }), ...mapState("modals/manageStation", { tab: state => state.tab, station: state => state.station, originalStation: state => state.originalStation, songsList: state => state.songsList, stationPlaylist: state => state.stationPlaylist, includedPlaylists: state => state.includedPlaylists, excludedPlaylists: state => state.excludedPlaylists, stationPaused: state => state.stationPaused, currentSong: state => state.currentSong }), ...mapGetters({ socket: "websockets/getSocket" }) }, mounted() { this.socket.dispatch(`stations.getStationById`, this.stationId, res => { if (res.status === "success") { const { station } = res.data; this.editStation(station); if (!this.isOwnerOrAdmin() && this.station.partyMode) this.showTab("songs"); const currentSong = res.data.station.currentSong ? res.data.station.currentSong : {}; this.updateCurrentSong(currentSong); this.updateStationPaused(res.data.station.paused); this.socket.dispatch( "stations.getStationIncludedPlaylistsById", this.stationId, res => { if (res.status === "success") this.setIncludedPlaylists(res.data.playlists); } ); this.socket.dispatch( "stations.getStationExcludedPlaylistsById", this.stationId, res => { if (res.status === "success") this.setExcludedPlaylists(res.data.playlists); } ); if (this.isOwnerOrAdmin()) { this.socket.dispatch( "playlists.getPlaylistForStation", this.station._id, true, res => { if (res.status === "success") { this.updateStationPlaylist(res.data.playlist); } } ); } this.socket.dispatch( "stations.getQueue", this.stationId, res => { if (res.status === "success") this.updateSongsList(res.data.queue); } ); this.socket.dispatch( "apis.joinRoom", `manage-station.${this.stationId}` ); this.socket.on( "event:station.name.updated", res => { this.station.name = res.data.name; }, { modal: "manageStation" } ); this.socket.on( "event:station.displayName.updated", res => { this.station.displayName = res.data.displayName; }, { modal: "manageStation" } ); this.socket.on( "event:station.description.updated", res => { this.station.description = res.data.description; }, { modal: "manageStation" } ); this.socket.on( "event:station.partyMode.updated", res => { if (this.station.type === "community") this.station.partyMode = res.data.partyMode; }, { modal: "manageStation" } ); this.socket.on( "event:station.playMode.updated", res => { this.station.playMode = res.data.playMode; }, { modal: "manageStation" } ); this.socket.on( "event:station.theme.updated", res => { const { theme } = res.data; this.station.theme = theme; }, { modal: "manageStation" } ); this.socket.on( "event:station.privacy.updated", res => { this.station.privacy = res.data.privacy; }, { modal: "manageStation" } ); this.socket.on( "event:station.queue.lock.toggled", res => { this.station.locked = res.data.locked; }, { modal: "manageStation" } ); this.socket.on( "event:station.includedPlaylist", res => { const { playlist } = res.data; const playlistIndex = this.includedPlaylists .map(includedPlaylist => includedPlaylist._id) .indexOf(playlist._id); if (playlistIndex === -1) this.includedPlaylists.push(playlist); }, { modal: "manageStation" } ); this.socket.on( "event:station.excludedPlaylist", res => { const { playlist } = res.data; const playlistIndex = this.excludedPlaylists .map(excludedPlaylist => excludedPlaylist._id) .indexOf(playlist._id); if (playlistIndex === -1) this.excludedPlaylists.push(playlist); }, { modal: "manageStation" } ); this.socket.on( "event:station.removedIncludedPlaylist", res => { const { playlistId } = res.data; const playlistIndex = this.includedPlaylists .map(playlist => playlist._id) .indexOf(playlistId); if (playlistIndex >= 0) this.includedPlaylists.splice(playlistIndex, 1); }, { modal: "manageStation" } ); this.socket.on( "event:station.removedExcludedPlaylist", res => { const { playlistId } = res.data; const playlistIndex = this.excludedPlaylists .map(playlist => playlist._id) .indexOf(playlistId); if (playlistIndex >= 0) this.excludedPlaylists.splice(playlistIndex, 1); }, { modal: "manageStation" } ); } else { new Toast(`Station with that ID not found`); this.closeModal("manageStation"); } }); this.socket.on( "event:station.queue.updated", res => this.updateSongsList(res.data.queue), { modal: "manageStation" } ); this.socket.on( "event:station.queue.song.repositioned", res => this.repositionSongInList(res.data.song), { modal: "manageStation" } ); this.socket.on( "event:station.pause", () => this.updateStationPaused(true), { modal: "manageStation" } ); this.socket.on( "event:station.resume", () => this.updateStationPaused(false), { modal: "manageStation" } ); this.socket.on( "event:station.nextSong", res => { const { currentSong } = res.data; this.updateCurrentSong(currentSong || {}); }, { modal: "manageStation" } ); if (this.isOwnerOrAdmin()) { this.socket.on( "event:playlist.song.added", res => { if (this.stationPlaylist._id === res.data.playlistId) this.stationPlaylist.songs.push(res.data.song); }, { modal: "manageStation" } ); this.socket.on( "event:playlist.song.removed", res => { if (this.stationPlaylist._id === res.data.playlistId) { // remove song from array of playlists this.stationPlaylist.songs.forEach((song, index) => { if (song.youtubeId === res.data.youtubeId) this.stationPlaylist.songs.splice(index, 1); }); } }, { modal: "manageStation" } ); this.socket.on( "event:playlist.songs.repositioned", res => { if (this.stationPlaylist._id === res.data.playlistId) { // for each song that has a new position res.data.songsBeingChanged.forEach(changedSong => { this.stationPlaylist.songs.forEach( (song, index) => { // find song locally if ( song.youtubeId === changedSong.youtubeId ) { // change song position attribute this.stationPlaylist.songs[ index ].position = changedSong.position; // reposition in array if needed if (index !== changedSong.position - 1) this.stationPlaylist.songs.splice( changedSong.position - 1, 0, this.stationPlaylist.songs.splice( index, 1 )[0] ); } } ); }); } }, { modal: "manageStation" } ); } }, beforeUnmount() { this.socket.dispatch( "apis.leaveRoom", `manage-station.${this.stationId}`, () => {} ); this.clearStation(); this.showTab("settings"); }, methods: { isOwner() { return ( this.loggedIn && this.station && this.userId === this.station.owner ); }, isAdmin() { return this.loggedIn && this.role === "admin"; }, isOwnerOrAdmin() { return this.isOwner() || this.isAdmin(); }, isPartyMode() { return ( this.station && this.station.type === "community" && this.station.partyMode ); }, isAllowedToParty() { return ( this.station && this.isPartyMode() && (!this.station.locked || this.isOwnerOrAdmin()) && this.loggedIn ); }, isPlaylistMode() { return this.station && !this.isPartyMode(); }, removeStation() { this.socket.dispatch("stations.remove", this.station._id, res => { new Toast(res.message); if (res.status === "success") { this.closeModal("manageStation"); } }); }, resumeStation() { this.socket.dispatch("stations.resume", this.station._id, res => { if (res.status !== "success") new Toast(`Error: ${res.message}`); else new Toast("Successfully resumed the station."); }); }, pauseStation() { this.socket.dispatch("stations.pause", this.station._id, res => { if (res.status !== "success") new Toast(`Error: ${res.message}`); else new Toast("Successfully paused the station."); }); }, skipStation() { this.socket.dispatch( "stations.forceSkip", this.station._id, res => { if (res.status !== "success") new Toast(`Error: ${res.message}`); else new Toast( "Successfully skipped the station's current song." ); } ); }, clearAndRefillStationQueue() { this.socket.dispatch( "stations.clearAndRefillStationQueue", this.station._id, res => { if (res.status !== "success") new Toast({ content: `Error: ${res.message}`, timeout: 8000 }); else new Toast({ content: res.message, timeout: 4000 }); } ); }, ...mapActions("modals/manageStation", [ "editStation", "setIncludedPlaylists", "setExcludedPlaylists", "clearStation", "updateSongsList", "updateStationPlaylist", "repositionSongInList", "updateStationPaused", "updateCurrentSong" ]), ...mapActions({ showTab(dispatch, payload) { this.$refs[`${payload}-tab`].scrollIntoView(); return dispatch("modals/manageStation/showTab", payload); } }), ...mapActions("modalVisibility", ["openModal", "closeModal"]), ...mapActions("user/playlists", ["editPlaylist"]) } }; </script> <style lang="scss"> .manage-station-modal.modal { z-index: 1800; .modal-card { width: 1300px; height: 100%; overflow: auto; .tab > button { width: 100%; margin-bottom: 10px; } .currently-playing.song-item { .song-info { width: calc(100% - 150px); } .thumbnail { min-width: 130px; width: 130px; height: 130px; } } } } </style> <style lang="scss" scoped> .night-mode { .manage-station-modal.modal .modal-card-body .custom-modal-body { .left-section { #about-station-container { background-color: var(--dark-grey-3) !important; border: 0; } .section { background-color: transparent !important; } .tab-selection .button { background: var(--dark-grey); color: var(--white); } .tab { background-color: var(--dark-grey-3); border: 0; } } .right-section .section, #queue { border-radius: 5px; background-color: transparent !important; } } } .manage-station-modal.modal .modal-card-body .custom-modal-body { display: flex; flex-wrap: wrap; height: 100%; .section { display: flex; flex-direction: column; flex-grow: 1; width: auto; padding: 15px !important; margin: 0 10px; } .left-section { flex-basis: 50%; height: 100%; overflow-y: auto; flex-grow: 1; .section:first-child { padding: 0 15px 15px !important; } #about-station-container { padding: 20px; display: flex; flex-direction: column; flex-grow: unset; border-radius: 5px; margin: 0 0 20px 0; background-color: var(--white); border: 1px solid var(--light-grey-3); #station-info { #station-name { flex-direction: row !important; display: flex; flex-direction: row; max-width: 100%; h1 { margin: 0; font-size: 36px; line-height: 0.8; } i { margin-left: 10px; font-size: 30px; color: var(--yellow); &.stationMode { padding-left: 10px; margin-left: auto; color: var(--primary-color); } } .verified-station { color: var(--primary-color); } } p { max-width: 700px; margin-bottom: 10px; } } #admin-buttons { display: flex; .button { margin: 3px; } } } .tab-selection { display: flex; overflow-x: auto; .button { border-radius: 5px 5px 0 0; border: 0; text-transform: uppercase; font-size: 14px; color: var(--dark-grey-3); background-color: var(--light-grey-2); flex-grow: 1; height: 32px; &:not(:first-of-type) { margin-left: 5px; } } .selected { background-color: var(--primary-color) !important; color: var(--white) !important; font-weight: 600; } } .tab { border: 1px solid var(--light-grey-3); padding: 15px; border-radius: 0 0 5px 5px; } } .right-section { flex-basis: 50%; height: 100%; overflow-y: auto; flex-grow: 1; .section { .queue-title { display: flex; line-height: 30px; .material-icons { margin-left: 5px; margin-bottom: 5px; font-size: 28px; cursor: pointer; &:first-of-type { margin-left: auto; } &.skip-station { color: var(--red); } &.resume-station, &.pause-station { color: var(--primary-color); } } } .currently-playing { margin-bottom: 10px; } } } } @media screen and (max-width: 1100px) { .manage-station-modal.modal .modal-card-body .custom-modal-body { .left-section, .right-section { flex-basis: unset; height: auto; } } } </style>