1
0

index.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672
  1. <template>
  2. <modal
  3. v-if="station"
  4. :title="
  5. !isOwnerOrAdmin() && station.partyMode
  6. ? 'Add Song to Queue'
  7. : 'Manage Station'
  8. "
  9. class="manage-station-modal"
  10. >
  11. <template #body>
  12. <div class="custom-modal-body" v-if="station && station._id">
  13. <div class="left-section">
  14. <div class="section tabs-container">
  15. <div class="tab-selection">
  16. <button
  17. v-if="isOwnerOrAdmin()"
  18. class="button is-default"
  19. :class="{ selected: tab === 'settings' }"
  20. @click="showTab('settings')"
  21. >
  22. Settings
  23. </button>
  24. <button
  25. v-if="
  26. isOwnerOrAdmin() ||
  27. (loggedIn &&
  28. station.type === 'community' &&
  29. station.partyMode &&
  30. ((station.locked &&
  31. isOwnerOrAdmin()) ||
  32. !station.locked))
  33. "
  34. class="button is-default"
  35. :class="{ selected: tab === 'playlists' }"
  36. @click="showTab('playlists')"
  37. >
  38. Playlists
  39. </button>
  40. <button
  41. v-if="
  42. loggedIn &&
  43. station.type === 'community' &&
  44. station.partyMode &&
  45. ((station.locked && isOwnerOrAdmin()) ||
  46. !station.locked)
  47. "
  48. class="button is-default"
  49. :class="{ selected: tab === 'search' }"
  50. @click="showTab('search')"
  51. >
  52. Search
  53. </button>
  54. <button
  55. v-if="isOwnerOrAdmin()"
  56. class="button is-default"
  57. :class="{ selected: tab === 'blacklist' }"
  58. @click="showTab('blacklist')"
  59. >
  60. Blacklist
  61. </button>
  62. </div>
  63. <settings
  64. v-if="isOwnerOrAdmin()"
  65. class="tab"
  66. v-show="tab === 'settings'"
  67. />
  68. <playlists
  69. v-if="
  70. isOwnerOrAdmin() ||
  71. (loggedIn &&
  72. station.type === 'community' &&
  73. station.partyMode &&
  74. ((station.locked && isOwnerOrAdmin()) ||
  75. !station.locked))
  76. "
  77. class="tab"
  78. v-show="tab === 'playlists'"
  79. />
  80. <search
  81. v-if="
  82. loggedIn &&
  83. station.type === 'community' &&
  84. station.partyMode &&
  85. ((station.locked && isOwnerOrAdmin()) ||
  86. !station.locked)
  87. "
  88. class="tab"
  89. v-show="tab === 'search'"
  90. />
  91. <blacklist
  92. v-if="isOwnerOrAdmin()"
  93. class="tab"
  94. v-show="tab === 'blacklist'"
  95. />
  96. </div>
  97. </div>
  98. <div class="right-section">
  99. <div class="section">
  100. <div class="queue-title">
  101. <h4 class="section-title">Queue</h4>
  102. <i
  103. v-if="isOwnerOrAdmin() && stationPaused"
  104. @click="resumeStation()"
  105. class="material-icons resume-station"
  106. content="Resume Station"
  107. v-tippy
  108. >
  109. play_arrow
  110. </i>
  111. <i
  112. v-if="isOwnerOrAdmin() && !stationPaused"
  113. @click="pauseStation()"
  114. class="material-icons pause-station"
  115. content="Pause Station"
  116. v-tippy
  117. >
  118. pause
  119. </i>
  120. <confirm
  121. v-if="isOwnerOrAdmin()"
  122. @confirm="skipStation()"
  123. >
  124. <i
  125. class="material-icons skip-station"
  126. content="Force Skip Station"
  127. v-tippy
  128. >
  129. skip_next
  130. </i>
  131. </confirm>
  132. </div>
  133. <hr class="section-horizontal-rule" />
  134. <song-item
  135. v-if="currentSong._id"
  136. :song="currentSong"
  137. :requested-by="
  138. station.type === 'community' &&
  139. station.partyMode === true
  140. "
  141. header="Currently Playing.."
  142. class="currently-playing"
  143. />
  144. <queue sector="manageStation" />
  145. </div>
  146. </div>
  147. </div>
  148. </template>
  149. <template #footer>
  150. <router-link
  151. v-if="sector !== 'station' && station.name"
  152. :to="{
  153. name: 'station',
  154. params: { id: station.name }
  155. }"
  156. class="button is-primary"
  157. >
  158. Go To Station
  159. </router-link>
  160. <a
  161. class="button is-default"
  162. v-if="isOwnerOrAdmin() && !station.partyMode"
  163. @click="stationPlaylist()"
  164. >
  165. View Station Playlist
  166. </a>
  167. <button
  168. class="button is-primary tab-actionable-button"
  169. v-if="loggedIn && station.type === 'official'"
  170. @click="openModal('requestSong')"
  171. >
  172. <i class="material-icons icon-with-button">queue</i>
  173. <span class="optional-desktop-only-text"> Request Song </span>
  174. </button>
  175. <div v-if="isOwnerOrAdmin()" class="right">
  176. <confirm @confirm="clearAndRefillStationQueue()">
  177. <a class="button is-danger">
  178. Clear and refill station queue
  179. </a>
  180. </confirm>
  181. <confirm
  182. v-if="station && station.type === 'community'"
  183. @confirm="removeStation()"
  184. >
  185. <button class="button is-danger">Delete station</button>
  186. </confirm>
  187. </div>
  188. </template>
  189. </modal>
  190. </template>
  191. <script>
  192. import { mapState, mapGetters, mapActions } from "vuex";
  193. import Toast from "toasters";
  194. import Confirm from "@/components/Confirm.vue";
  195. import Queue from "@/components/Queue.vue";
  196. import SongItem from "@/components/SongItem.vue";
  197. import Modal from "../../Modal.vue";
  198. import Settings from "./Tabs/Settings.vue";
  199. import Playlists from "./Tabs/Playlists.vue";
  200. import Search from "./Tabs/Search.vue";
  201. import Blacklist from "./Tabs/Blacklist.vue";
  202. export default {
  203. components: {
  204. Modal,
  205. Confirm,
  206. Queue,
  207. SongItem,
  208. Settings,
  209. Playlists,
  210. Search,
  211. Blacklist
  212. },
  213. props: {
  214. stationId: { type: String, default: "" },
  215. sector: { type: String, default: "admin" }
  216. },
  217. computed: {
  218. ...mapState({
  219. loggedIn: state => state.user.auth.loggedIn,
  220. userId: state => state.user.auth.userId,
  221. role: state => state.user.auth.role
  222. }),
  223. ...mapState("modals/manageStation", {
  224. tab: state => state.tab,
  225. station: state => state.station,
  226. originalStation: state => state.originalStation,
  227. songsList: state => state.songsList,
  228. includedPlaylists: state => state.includedPlaylists,
  229. excludedPlaylists: state => state.excludedPlaylists,
  230. stationPaused: state => state.stationPaused,
  231. currentSong: state => state.currentSong
  232. }),
  233. ...mapGetters({
  234. socket: "websockets/getSocket"
  235. })
  236. },
  237. mounted() {
  238. this.socket.dispatch(`stations.getStationById`, this.stationId, res => {
  239. if (res.status === "success") {
  240. const { station } = res.data;
  241. this.editStation(station);
  242. if (!this.isOwnerOrAdmin() && this.station.partyMode)
  243. this.showTab("search");
  244. const currentSong = res.data.station.currentSong
  245. ? res.data.station.currentSong
  246. : {};
  247. this.updateCurrentSong(currentSong);
  248. this.updateStationPaused(res.data.station.paused);
  249. this.socket.dispatch(
  250. "stations.getStationIncludedPlaylistsById",
  251. this.stationId,
  252. res => {
  253. if (res.status === "success")
  254. this.setIncludedPlaylists(res.data.playlists);
  255. }
  256. );
  257. this.socket.dispatch(
  258. "stations.getStationExcludedPlaylistsById",
  259. this.stationId,
  260. res => {
  261. if (res.status === "success")
  262. this.setExcludedPlaylists(res.data.playlists);
  263. }
  264. );
  265. this.socket.dispatch(
  266. "stations.getQueue",
  267. this.stationId,
  268. res => {
  269. if (res.status === "success")
  270. this.updateSongsList(res.data.queue);
  271. }
  272. );
  273. this.socket.dispatch(
  274. "apis.joinRoom",
  275. `manage-station.${this.stationId}`
  276. );
  277. this.socket.on(
  278. "event:station.updateName",
  279. res => {
  280. this.station.name = res.data.name;
  281. },
  282. { modal: "manageStation" }
  283. );
  284. this.socket.on(
  285. "event:station.updateDisplayName",
  286. res => {
  287. this.station.displayName = res.data.displayName;
  288. },
  289. { modal: "manageStation" }
  290. );
  291. this.socket.on(
  292. "event:station.updateDescription",
  293. res => {
  294. this.station.description = res.data.description;
  295. },
  296. { modal: "manageStation" }
  297. );
  298. this.socket.on(
  299. "event:partyMode.updated",
  300. res => {
  301. if (this.station.type === "community")
  302. this.station.partyMode = res.data.partyMode;
  303. },
  304. { modal: "manageStation" }
  305. );
  306. this.socket.on(
  307. "event:playMode.updated",
  308. res => {
  309. this.station.playMode = res.data.playMode;
  310. },
  311. { modal: "manageStation" }
  312. );
  313. this.socket.on(
  314. "event:station.themeUpdated",
  315. res => {
  316. const { theme } = res.data;
  317. this.station.theme = theme;
  318. },
  319. { modal: "manageStation" }
  320. );
  321. this.socket.on(
  322. "event:station.updatePrivacy",
  323. res => {
  324. this.station.privacy = res.data.privacy;
  325. },
  326. { modal: "manageStation" }
  327. );
  328. this.socket.on(
  329. "event:queueLockToggled",
  330. res => {
  331. this.station.locked = res.data.locked;
  332. },
  333. { modal: "manageStation" }
  334. );
  335. this.socket.on(
  336. "event:station.includedPlaylist",
  337. res => {
  338. const { playlist } = res.data;
  339. const playlistIndex = this.includedPlaylists
  340. .map(includedPlaylist => includedPlaylist._id)
  341. .indexOf(playlist._id);
  342. if (playlistIndex === -1)
  343. this.includedPlaylists.push(playlist);
  344. },
  345. { modal: "manageStation" }
  346. );
  347. this.socket.on(
  348. "event:station.excludedPlaylist",
  349. res => {
  350. const { playlist } = res.data;
  351. const playlistIndex = this.excludedPlaylists
  352. .map(excludedPlaylist => excludedPlaylist._id)
  353. .indexOf(playlist._id);
  354. if (playlistIndex === -1)
  355. this.excludedPlaylists.push(playlist);
  356. },
  357. { modal: "manageStation" }
  358. );
  359. this.socket.on(
  360. "event:station.removedIncludedPlaylist",
  361. res => {
  362. const { playlistId } = res.data;
  363. const playlistIndex = this.includedPlaylists
  364. .map(playlist => playlist._id)
  365. .indexOf(playlistId);
  366. if (playlistIndex >= 0)
  367. this.includedPlaylists.splice(playlistIndex, 1);
  368. },
  369. { modal: "manageStation" }
  370. );
  371. this.socket.on(
  372. "event:station.removedExcludedPlaylist",
  373. res => {
  374. const { playlistId } = res.data;
  375. const playlistIndex = this.excludedPlaylists
  376. .map(playlist => playlist._id)
  377. .indexOf(playlistId);
  378. if (playlistIndex >= 0)
  379. this.excludedPlaylists.splice(playlistIndex, 1);
  380. },
  381. { modal: "manageStation" }
  382. );
  383. } else {
  384. new Toast(`Station with that ID not found`);
  385. this.closeModal("manageStation");
  386. }
  387. });
  388. this.socket.on(
  389. "event:queue.update",
  390. res => this.updateSongsList(res.data.queue),
  391. { modal: "manageStation" }
  392. );
  393. this.socket.on(
  394. "event:queue.repositionSong",
  395. res => this.repositionSongInList(res.data.song),
  396. { modal: "manageStation" }
  397. );
  398. this.socket.on(
  399. "event:stations.pause",
  400. () => this.updateStationPaused(true),
  401. { modal: "manageStation" }
  402. );
  403. this.socket.on(
  404. "event:stations.resume",
  405. () => this.updateStationPaused(false),
  406. { modal: "manageStation" }
  407. );
  408. this.socket.on(
  409. "event:songs.next",
  410. res => {
  411. const { currentSong } = res.data;
  412. this.updateCurrentSong(currentSong || {});
  413. },
  414. { modal: "manageStation" }
  415. );
  416. },
  417. beforeDestroy() {
  418. this.socket.dispatch(
  419. "apis.leaveRoom",
  420. `manage-station.${this.stationId}`,
  421. () => {}
  422. );
  423. this.repositionSongInList([]);
  424. this.clearStation();
  425. this.showTab("settings");
  426. },
  427. methods: {
  428. isOwner() {
  429. return this.loggedIn && this.userId === this.station.owner;
  430. },
  431. isAdmin() {
  432. return this.loggedIn && this.role === "admin";
  433. },
  434. isOwnerOrAdmin() {
  435. return this.isOwner() || this.isAdmin();
  436. },
  437. removeStation() {
  438. this.socket.dispatch("stations.remove", this.station._id, res => {
  439. new Toast(res.message);
  440. if (res.status === "success") {
  441. this.closeModal("manageStation");
  442. }
  443. });
  444. },
  445. resumeStation() {
  446. this.socket.dispatch("stations.resume", this.station._id, res => {
  447. if (res.status !== "success")
  448. new Toast(`Error: ${res.message}`);
  449. else new Toast("Successfully resumed the station.");
  450. });
  451. },
  452. pauseStation() {
  453. this.socket.dispatch("stations.pause", this.station._id, res => {
  454. if (res.status !== "success")
  455. new Toast(`Error: ${res.message}`);
  456. else new Toast("Successfully paused the station.");
  457. });
  458. },
  459. skipStation() {
  460. this.socket.dispatch(
  461. "stations.forceSkip",
  462. this.station._id,
  463. res => {
  464. if (res.status !== "success")
  465. new Toast(`Error: ${res.message}`);
  466. else
  467. new Toast(
  468. "Successfully skipped the station's current song."
  469. );
  470. }
  471. );
  472. },
  473. clearAndRefillStationQueue() {
  474. this.socket.dispatch(
  475. "stations.clearAndRefillStationQueue",
  476. this.station._id,
  477. res => {
  478. if (res.status !== "success")
  479. new Toast({
  480. content: `Error: ${res.message}`,
  481. timeout: 8000
  482. });
  483. else new Toast({ content: res.message, timeout: 4000 });
  484. }
  485. );
  486. },
  487. stationPlaylist() {
  488. this.socket.dispatch(
  489. "playlists.getPlaylistForStation",
  490. this.station._id,
  491. false,
  492. res => {
  493. if (res.status === "success") {
  494. this.editPlaylist(res.data.playlist._id);
  495. this.openModal("editPlaylist");
  496. } else {
  497. new Toast(res.message);
  498. }
  499. }
  500. );
  501. },
  502. ...mapActions("modals/manageStation", [
  503. "showTab",
  504. "editStation",
  505. "setIncludedPlaylists",
  506. "setExcludedPlaylists",
  507. "clearStation",
  508. "updateSongsList",
  509. "repositionSongInList",
  510. "updateStationPaused",
  511. "updateCurrentSong"
  512. ]),
  513. ...mapActions("modalVisibility", ["openModal", "closeModal"]),
  514. ...mapActions("user/playlists", ["editPlaylist"])
  515. }
  516. };
  517. </script>
  518. <style lang="scss">
  519. .manage-station-modal.modal {
  520. z-index: 1800;
  521. .modal-card {
  522. width: 1300px;
  523. height: 100%;
  524. overflow: auto;
  525. .tab > button {
  526. width: 100%;
  527. margin-bottom: 10px;
  528. }
  529. .currently-playing.song-item {
  530. .song-info {
  531. width: calc(100% - 150px);
  532. }
  533. .thumbnail {
  534. min-width: 130px;
  535. width: 130px;
  536. height: 130px;
  537. }
  538. }
  539. }
  540. }
  541. </style>
  542. <style lang="scss" scoped>
  543. .manage-station-modal.modal .modal-card-body .custom-modal-body {
  544. display: flex;
  545. flex-wrap: wrap;
  546. height: 100%;
  547. .section {
  548. display: flex;
  549. flex-direction: column;
  550. flex-grow: 1;
  551. width: auto;
  552. padding: 15px !important;
  553. margin: 0 10px;
  554. }
  555. .left-section {
  556. flex-basis: 50%;
  557. height: 100%;
  558. overflow-y: auto;
  559. flex-grow: 1;
  560. .tabs-container {
  561. .tab-selection {
  562. display: flex;
  563. overflow-x: auto;
  564. .button {
  565. border-radius: 5px 5px 0 0;
  566. border: 0;
  567. text-transform: uppercase;
  568. font-size: 14px;
  569. color: var(--dark-grey-3);
  570. background-color: var(--light-grey-2);
  571. flex-grow: 1;
  572. height: 32px;
  573. &:not(:first-of-type) {
  574. margin-left: 5px;
  575. }
  576. }
  577. .selected {
  578. background-color: var(--primary-color) !important;
  579. color: var(--white) !important;
  580. font-weight: 600;
  581. }
  582. }
  583. .tab {
  584. border: 1px solid var(--light-grey-3);
  585. padding: 15px;
  586. border-radius: 0 0 5px 5px;
  587. }
  588. }
  589. }
  590. .right-section {
  591. flex-basis: 50%;
  592. height: 100%;
  593. overflow-y: auto;
  594. flex-grow: 1;
  595. .section {
  596. .queue-title {
  597. display: flex;
  598. line-height: 30px;
  599. .material-icons {
  600. margin-left: 5px;
  601. margin-bottom: 5px;
  602. font-size: 28px;
  603. cursor: pointer;
  604. &:first-of-type {
  605. margin-left: auto;
  606. }
  607. &.skip-station {
  608. color: var(--red);
  609. }
  610. &.resume-station,
  611. &.pause-station {
  612. color: var(--primary-color);
  613. }
  614. }
  615. }
  616. .currently-playing {
  617. margin-bottom: 10px;
  618. }
  619. }
  620. }
  621. }
  622. @media screen and (max-width: 1100px) {
  623. .manage-station-modal.modal .modal-card-body .custom-modal-body {
  624. .left-section,
  625. .right-section {
  626. flex-basis: unset;
  627. height: auto;
  628. }
  629. }
  630. }
  631. </style>