Playlists.vue 24 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024
  1. <template>
  2. <div class="station-playlists">
  3. <div class="tabs-container">
  4. <div class="tab-selection">
  5. <button
  6. class="button is-default"
  7. ref="search-tab"
  8. :class="{ selected: tab === 'search' }"
  9. @click="showTab('search')"
  10. >
  11. Search
  12. </button>
  13. <button
  14. v-if="station.type === 'community'"
  15. class="button is-default"
  16. ref="my-playlists-tab"
  17. :class="{ selected: tab === 'my-playlists' }"
  18. @click="showTab('my-playlists')"
  19. >
  20. My Playlists
  21. </button>
  22. <button
  23. class="button is-default"
  24. ref="party-tab"
  25. :class="{ selected: tab === 'party' }"
  26. v-if="isPartyMode()"
  27. @click="showTab('party')"
  28. >
  29. Party
  30. </button>
  31. <button
  32. class="button is-default"
  33. ref="included-tab"
  34. :class="{ selected: tab === 'included' }"
  35. v-if="isPlaylistMode()"
  36. @click="showTab('included')"
  37. >
  38. Included
  39. </button>
  40. <button
  41. class="button is-default"
  42. ref="excluded-tab"
  43. :class="{ selected: tab === 'excluded' }"
  44. @click="showTab('excluded')"
  45. >
  46. Excluded
  47. </button>
  48. </div>
  49. <div class="tab" v-show="tab === 'search'">
  50. <label class="label"> Search for a public playlist </label>
  51. <div class="control is-grouped input-with-button">
  52. <p class="control is-expanded">
  53. <input
  54. class="input"
  55. type="text"
  56. placeholder="Enter your playlist query here..."
  57. v-model="search.query"
  58. @keyup.enter="searchForPlaylists(1)"
  59. />
  60. </p>
  61. <p class="control">
  62. <a class="button is-info" @click="searchForPlaylists(1)"
  63. ><i class="material-icons icon-with-button"
  64. >search</i
  65. >Search</a
  66. >
  67. </p>
  68. </div>
  69. <div v-if="search.results.length > 0">
  70. <playlist-item
  71. v-for="playlist in search.results"
  72. :key="`searchKey-${playlist._id}`"
  73. :playlist="playlist"
  74. :show-owner="true"
  75. >
  76. <template #item-icon>
  77. <i
  78. class="material-icons"
  79. v-if="
  80. isAllowedToParty() &&
  81. isSelected(playlist._id)
  82. "
  83. content="This playlist is currently selected"
  84. v-tippy
  85. >
  86. radio
  87. </i>
  88. <i
  89. class="material-icons"
  90. v-else-if="
  91. isOwnerOrAdmin() &&
  92. isPlaylistMode() &&
  93. isIncluded(playlist._id)
  94. "
  95. content="This playlist is currently included"
  96. v-tippy
  97. >
  98. play_arrow
  99. </i>
  100. <i
  101. class="material-icons excluded-icon"
  102. v-else-if="
  103. isOwnerOrAdmin() && isExcluded(playlist._id)
  104. "
  105. content="This playlist is currently excluded"
  106. v-tippy
  107. >
  108. block
  109. </i>
  110. <i
  111. class="material-icons"
  112. v-else
  113. :content="
  114. isPartyMode()
  115. ? 'This playlist is currently not selected or excluded'
  116. : 'This playlist is currently not included or excluded'
  117. "
  118. v-tippy
  119. >
  120. play_disabled
  121. </i>
  122. </template>
  123. <template #actions>
  124. <i
  125. v-if="isExcluded(playlist._id)"
  126. class="material-icons stop-icon"
  127. content="This playlist is blacklisted in this station"
  128. v-tippy="{ theme: 'info' }"
  129. >play_disabled</i
  130. >
  131. <confirm
  132. v-if="isPartyMode() && isSelected(playlist._id)"
  133. @confirm="deselectPartyPlaylist(playlist._id)"
  134. >
  135. <i
  136. class="material-icons stop-icon"
  137. content="Stop playing songs from this playlist"
  138. v-tippy
  139. >
  140. stop
  141. </i>
  142. </confirm>
  143. <confirm
  144. v-if="
  145. isOwnerOrAdmin() &&
  146. isPlaylistMode() &&
  147. isIncluded(playlist._id)
  148. "
  149. @confirm="removeIncludedPlaylist(playlist._id)"
  150. >
  151. <i
  152. class="material-icons stop-icon"
  153. content="Stop playing songs from this playlist"
  154. v-tippy
  155. >
  156. stop
  157. </i>
  158. </confirm>
  159. <i
  160. v-if="
  161. isPartyMode() &&
  162. !isSelected(playlist._id) &&
  163. !isExcluded(playlist._id)
  164. "
  165. @click="selectPartyPlaylist(playlist)"
  166. class="material-icons play-icon"
  167. content="Request songs from this playlist"
  168. v-tippy
  169. >play_arrow</i
  170. >
  171. <i
  172. v-if="
  173. isOwnerOrAdmin() &&
  174. isPlaylistMode() &&
  175. !isIncluded(playlist._id) &&
  176. !isExcluded(playlist._id)
  177. "
  178. @click="includePlaylist(playlist)"
  179. class="material-icons play-icon"
  180. :content="'Play songs from this playlist'"
  181. v-tippy
  182. >play_arrow</i
  183. >
  184. <confirm
  185. v-if="
  186. isOwnerOrAdmin() &&
  187. !isExcluded(playlist._id)
  188. "
  189. @confirm="blacklistPlaylist(playlist._id)"
  190. >
  191. <i
  192. class="material-icons stop-icon"
  193. content="Blacklist Playlist"
  194. v-tippy
  195. >block</i
  196. >
  197. </confirm>
  198. <confirm
  199. v-if="
  200. isOwnerOrAdmin() && isExcluded(playlist._id)
  201. "
  202. @confirm="removeExcludedPlaylist(playlist._id)"
  203. >
  204. <i
  205. class="material-icons stop-icon"
  206. content="Stop blacklisting songs from this playlist"
  207. v-tippy
  208. >
  209. stop
  210. </i>
  211. </confirm>
  212. <i
  213. v-if="playlist.createdBy === myUserId"
  214. @click="showPlaylist(playlist._id)"
  215. class="material-icons edit-icon"
  216. content="Edit Playlist"
  217. v-tippy
  218. >edit</i
  219. >
  220. <i
  221. v-if="
  222. playlist.createdBy !== myUserId &&
  223. (playlist.privacy === 'public' ||
  224. isAdmin())
  225. "
  226. @click="showPlaylist(playlist._id)"
  227. class="material-icons edit-icon"
  228. content="View Playlist"
  229. v-tippy
  230. >visibility</i
  231. >
  232. </template>
  233. </playlist-item>
  234. <button
  235. v-if="resultsLeftCount > 0"
  236. class="button is-primary load-more-button"
  237. @click="searchForPlaylists(search.page + 1)"
  238. >
  239. Load {{ nextPageResultsCount }} more results
  240. </button>
  241. </div>
  242. </div>
  243. <div
  244. v-if="station.type === 'community'"
  245. class="tab"
  246. v-show="tab === 'my-playlists'"
  247. >
  248. <button
  249. class="button is-primary"
  250. id="create-new-playlist-button"
  251. @click="openModal('createPlaylist')"
  252. >
  253. Create new playlist
  254. </button>
  255. <div
  256. class="menu-list scrollable-list"
  257. v-if="playlists.length > 0"
  258. >
  259. <draggable
  260. tag="transition-group"
  261. :component-data="{
  262. name: !drag ? 'draggable-list-transition' : null
  263. }"
  264. item-key="_id"
  265. v-model="playlists"
  266. v-bind="dragOptions"
  267. @start="drag = true"
  268. @end="drag = false"
  269. @change="savePlaylistOrder"
  270. >
  271. <template #item="{element}">
  272. <playlist-item
  273. class="item-draggable"
  274. :playlist="element"
  275. >
  276. <template #item-icon>
  277. <i
  278. class="material-icons"
  279. v-if="
  280. isAllowedToParty() &&
  281. isSelected(element._id)
  282. "
  283. content="This playlist is currently selected"
  284. v-tippy
  285. >
  286. radio
  287. </i>
  288. <i
  289. class="material-icons"
  290. v-else-if="
  291. isOwnerOrAdmin() &&
  292. isPlaylistMode() &&
  293. isIncluded(element._id)
  294. "
  295. content="This playlist is currently included"
  296. v-tippy
  297. >
  298. play_arrow
  299. </i>
  300. <i
  301. class="material-icons excluded-icon"
  302. v-else-if="
  303. isOwnerOrAdmin() &&
  304. isExcluded(element._id)
  305. "
  306. content="This playlist is currently excluded"
  307. v-tippy
  308. >
  309. block
  310. </i>
  311. <i
  312. class="material-icons"
  313. v-else
  314. :content="
  315. isPartyMode()
  316. ? 'This playlist is currently not selected or excluded'
  317. : 'This playlist is currently not included or excluded'
  318. "
  319. v-tippy
  320. >
  321. play_disabled
  322. </i>
  323. </template>
  324. <template #actions>
  325. <!-- <i
  326. v-if="isExcluded(playlist._id)"
  327. class="material-icons stop-icon"
  328. content="This playlist is blacklisted in this station"
  329. v-tippy="{ theme: 'info' }"
  330. >play_disabled</i
  331. > -->
  332. <i
  333. v-if="
  334. isPartyMode() &&
  335. !isSelected(element._id)
  336. "
  337. @click="selectPartyPlaylist(element)"
  338. class="material-icons play-icon"
  339. content="Request songs from this playlist"
  340. v-tippy
  341. >play_arrow</i
  342. >
  343. <i
  344. v-if="
  345. isPlaylistMode() &&
  346. isOwnerOrAdmin() &&
  347. !isSelected(element._id)
  348. "
  349. @click="includePlaylist(element)"
  350. class="material-icons play-icon"
  351. content="Play songs from this playlist"
  352. v-tippy
  353. >play_arrow</i
  354. >
  355. <confirm
  356. v-if="
  357. isPartyMode() &&
  358. isSelected(element._id)
  359. "
  360. @confirm="
  361. deselectPartyPlaylist(element._id)
  362. "
  363. >
  364. <i
  365. class="material-icons stop-icon"
  366. content="Stop requesting songs from this playlist"
  367. v-tippy
  368. >stop</i
  369. >
  370. </confirm>
  371. <confirm
  372. v-if="
  373. isPlaylistMode() &&
  374. isOwnerOrAdmin() &&
  375. isIncluded(element._id)
  376. "
  377. @confirm="
  378. removeIncludedPlaylist(element._id)
  379. "
  380. >
  381. <i
  382. class="material-icons stop-icon"
  383. content="Stop playing songs from this playlist"
  384. v-tippy
  385. >stop</i
  386. >
  387. </confirm>
  388. <confirm
  389. v-if="
  390. isOwnerOrAdmin() &&
  391. !isExcluded(element._id)
  392. "
  393. @confirm="
  394. blacklistPlaylist(element._id)
  395. "
  396. >
  397. <i
  398. class="material-icons stop-icon"
  399. content="Blacklist Playlist"
  400. v-tippy
  401. >block</i
  402. >
  403. </confirm>
  404. <confirm
  405. v-if="
  406. isOwnerOrAdmin() &&
  407. isExcluded(element._id)
  408. "
  409. @confirm="
  410. removeExcludedPlaylist(element._id)
  411. "
  412. >
  413. <i
  414. class="material-icons stop-icon"
  415. content="Stop blacklisting songs from this playlist"
  416. v-tippy
  417. >
  418. stop
  419. </i>
  420. </confirm>
  421. <i
  422. @click="showPlaylist(element._id)"
  423. class="material-icons edit-icon"
  424. content="Edit Playlist"
  425. v-tippy
  426. >edit</i
  427. >
  428. </template>
  429. </playlist-item>
  430. </template>
  431. </draggable>
  432. </div>
  433. <p v-else class="has-text-centered scrollable-list">
  434. You don't have any playlists!
  435. </p>
  436. </div>
  437. <div class="tab" v-show="tab === 'party'" v-if="isPartyMode()">
  438. <div v-if="partyPlaylists.length > 0">
  439. <playlist-item
  440. v-for="playlist in partyPlaylists"
  441. :key="`key-${playlist._id}`"
  442. :playlist="playlist"
  443. :show-owner="true"
  444. >
  445. <template #item-icon>
  446. <i
  447. class="material-icons"
  448. content="This playlist is currently selected"
  449. v-tippy
  450. >
  451. radio
  452. </i>
  453. </template>
  454. <template #actions>
  455. <confirm
  456. v-if="isOwnerOrAdmin()"
  457. @confirm="deselectPartyPlaylist(playlist._id)"
  458. >
  459. <i
  460. class="material-icons stop-icon"
  461. content="Stop playing songs from this playlist"
  462. v-tippy
  463. >
  464. stop
  465. </i>
  466. </confirm>
  467. <confirm
  468. v-if="isOwnerOrAdmin()"
  469. @confirm="blacklistPlaylist(playlist._id)"
  470. >
  471. <i
  472. class="material-icons stop-icon"
  473. content="Blacklist Playlist"
  474. v-tippy
  475. >block</i
  476. >
  477. </confirm>
  478. <i
  479. v-if="playlist.createdBy === myUserId"
  480. @click="showPlaylist(playlist._id)"
  481. class="material-icons edit-icon"
  482. content="Edit Playlist"
  483. v-tippy
  484. >edit</i
  485. >
  486. <i
  487. v-if="
  488. playlist.createdBy !== myUserId &&
  489. (playlist.privacy === 'public' ||
  490. isAdmin())
  491. "
  492. @click="showPlaylist(playlist._id)"
  493. class="material-icons edit-icon"
  494. content="View Playlist"
  495. v-tippy
  496. >visibility</i
  497. >
  498. </template>
  499. </playlist-item>
  500. </div>
  501. <p v-else class="has-text-centered scrollable-list">
  502. No playlists currently being played.
  503. </p>
  504. </div>
  505. <div
  506. class="tab"
  507. v-show="tab === 'included'"
  508. v-if="isPlaylistMode()"
  509. >
  510. <div v-if="includedPlaylists.length > 0">
  511. <playlist-item
  512. v-for="playlist in includedPlaylists"
  513. :key="`key-${playlist._id}`"
  514. :playlist="playlist"
  515. :show-owner="true"
  516. >
  517. <template #item-icon>
  518. <i
  519. class="material-icons"
  520. content="This playlist is currently included"
  521. v-tippy
  522. >
  523. play_arrow
  524. </i>
  525. </template>
  526. <template #actions>
  527. <confirm
  528. v-if="isOwnerOrAdmin()"
  529. @confirm="removeIncludedPlaylist(playlist._id)"
  530. >
  531. <i
  532. class="material-icons stop-icon"
  533. content="Stop playing songs from this playlist"
  534. v-tippy
  535. >
  536. stop
  537. </i>
  538. </confirm>
  539. <confirm
  540. v-if="isOwnerOrAdmin()"
  541. @confirm="blacklistPlaylist(playlist._id)"
  542. >
  543. <i
  544. class="material-icons stop-icon"
  545. content="Blacklist Playlist"
  546. v-tippy
  547. >block</i
  548. >
  549. </confirm>
  550. <i
  551. v-if="playlist.createdBy === myUserId"
  552. @click="showPlaylist(playlist._id)"
  553. class="material-icons edit-icon"
  554. content="Edit Playlist"
  555. v-tippy
  556. >edit</i
  557. >
  558. <i
  559. v-if="
  560. playlist.createdBy !== myUserId &&
  561. (playlist.privacy === 'public' ||
  562. isAdmin())
  563. "
  564. @click="showPlaylist(playlist._id)"
  565. class="material-icons edit-icon"
  566. content="View Playlist"
  567. v-tippy
  568. >visibility</i
  569. >
  570. </template>
  571. </playlist-item>
  572. </div>
  573. <p v-else class="has-text-centered scrollable-list">
  574. No playlists currently included.
  575. </p>
  576. </div>
  577. <div
  578. class="tab"
  579. v-show="tab === 'excluded'"
  580. v-if="isOwnerOrAdmin()"
  581. >
  582. <div v-if="excludedPlaylists.length > 0">
  583. <playlist-item
  584. :playlist="playlist"
  585. v-for="playlist in excludedPlaylists"
  586. :key="`key-${playlist._id}`"
  587. >
  588. <template #item-icon>
  589. <i
  590. class="material-icons excluded-icon"
  591. content="This playlist is currently excluded"
  592. v-tippy
  593. >
  594. block
  595. </i>
  596. </template>
  597. <template #actions>
  598. <confirm
  599. @confirm="removeExcludedPlaylist(playlist._id)"
  600. >
  601. <i
  602. class="material-icons stop-icon"
  603. content="Stop blacklisting songs from this playlist
  604. "
  605. v-tippy
  606. >stop</i
  607. >
  608. </confirm>
  609. <i
  610. v-if="playlist.createdBy === userId"
  611. @click="showPlaylist(playlist._id)"
  612. class="material-icons edit-icon"
  613. content="Edit Playlist"
  614. v-tippy
  615. >edit</i
  616. >
  617. <i
  618. v-else
  619. @click="showPlaylist(playlist._id)"
  620. class="material-icons edit-icon"
  621. content="View Playlist"
  622. v-tippy
  623. >visibility</i
  624. >
  625. </template>
  626. </playlist-item>
  627. </div>
  628. <p v-else class="has-text-centered scrollable-list">
  629. No playlists currently excluded.
  630. </p>
  631. </div>
  632. </div>
  633. </div>
  634. </template>
  635. <script>
  636. import { mapActions, mapState, mapGetters } from "vuex";
  637. import Toast from "toasters";
  638. import PlaylistItem from "@/components/PlaylistItem.vue";
  639. import Confirm from "@/components/Confirm.vue";
  640. import SortablePlaylists from "@/mixins/SortablePlaylists.vue";
  641. export default {
  642. components: {
  643. PlaylistItem,
  644. Confirm
  645. },
  646. mixins: [SortablePlaylists],
  647. data() {
  648. return {
  649. tab: "included",
  650. search: {
  651. query: "",
  652. searchedQuery: "",
  653. page: 0,
  654. count: 0,
  655. resultsLeft: 0,
  656. results: []
  657. }
  658. };
  659. },
  660. computed: {
  661. resultsLeftCount() {
  662. return this.search.count - this.search.results.length;
  663. },
  664. nextPageResultsCount() {
  665. return Math.min(this.search.pageSize, this.resultsLeftCount);
  666. },
  667. ...mapState({
  668. loggedIn: state => state.user.auth.loggedIn,
  669. role: state => state.user.auth.role,
  670. userId: state => state.user.auth.userId,
  671. partyPlaylists: state => state.station.partyPlaylists
  672. }),
  673. ...mapState("modals/manageStation", {
  674. parentTab: state => state.tab,
  675. originalStation: state => state.originalStation,
  676. station: state => state.station,
  677. includedPlaylists: state => state.includedPlaylists,
  678. excludedPlaylists: state => state.excludedPlaylists,
  679. songsList: state => state.songsList
  680. }),
  681. ...mapGetters({
  682. socket: "websockets/getSocket"
  683. })
  684. },
  685. watch: {
  686. // eslint-disable-next-line func-names
  687. parentTab(value) {
  688. if (value === "playlists") {
  689. if (this.tab === "included" && this.isPartyMode()) {
  690. this.showTab("party");
  691. } else if (this.tab === "party" && this.isPlaylistMode()) {
  692. this.showTab("included");
  693. }
  694. }
  695. }
  696. },
  697. mounted() {
  698. if (this.station.type === "community" && this.station.partyMode)
  699. this.showTab("search");
  700. this.socket.dispatch("playlists.indexMyPlaylists", true, res => {
  701. if (res.status === "success") this.setPlaylists(res.data.playlists);
  702. this.orderOfPlaylists = this.calculatePlaylistOrder(); // order in regards to the database
  703. });
  704. this.socket.dispatch(
  705. `stations.getStationIncludedPlaylistsById`,
  706. this.station._id,
  707. res => {
  708. if (res.status === "success") {
  709. this.station.includedPlaylists = res.data.playlists;
  710. this.originalStation.includedPlaylists = res.data.playlists;
  711. }
  712. }
  713. );
  714. this.socket.dispatch(
  715. `stations.getStationExcludedPlaylistsById`,
  716. this.station._id,
  717. res => {
  718. if (res.status === "success") {
  719. this.station.excludedPlaylists = res.data.playlists;
  720. this.originalStation.excludedPlaylists = res.data.playlists;
  721. }
  722. }
  723. );
  724. },
  725. methods: {
  726. showTab(tab) {
  727. this.$refs[`${tab}-tab`].scrollIntoView();
  728. this.tab = tab;
  729. },
  730. isOwner() {
  731. return (
  732. this.loggedIn &&
  733. this.station &&
  734. this.userId === this.station.owner
  735. );
  736. },
  737. isAdmin() {
  738. return this.loggedIn && this.role === "admin";
  739. },
  740. isOwnerOrAdmin() {
  741. return this.isOwner() || this.isAdmin();
  742. },
  743. isPartyMode() {
  744. return (
  745. this.station &&
  746. this.station.type === "community" &&
  747. this.station.partyMode
  748. );
  749. },
  750. isAllowedToParty() {
  751. return (
  752. this.station &&
  753. this.isPartyMode() &&
  754. (!this.station.locked || this.isOwnerOrAdmin()) &&
  755. this.loggedIn
  756. );
  757. },
  758. isPlaylistMode() {
  759. return this.station && !this.isPartyMode();
  760. },
  761. showPlaylist(playlistId) {
  762. this.editPlaylist(playlistId);
  763. this.openModal("editPlaylist");
  764. },
  765. selectPartyPlaylist(playlist) {
  766. if (!this.isSelected(playlist.id)) {
  767. this.partyPlaylists.push(playlist);
  768. this.addPartyPlaylistSongToQueue();
  769. new Toast(
  770. "Successfully selected playlist to auto request songs."
  771. );
  772. } else {
  773. new Toast("Error: Playlist already selected.");
  774. }
  775. },
  776. includePlaylist(playlist) {
  777. this.socket.dispatch(
  778. "stations.includePlaylist",
  779. this.station._id,
  780. playlist._id,
  781. res => {
  782. new Toast(res.message);
  783. }
  784. );
  785. },
  786. deselectPartyPlaylist(id) {
  787. return new Promise(resolve => {
  788. let selected = false;
  789. this.partyPlaylists.forEach((playlist, index) => {
  790. if (playlist._id === id) {
  791. selected = true;
  792. this.partyPlaylists.splice(index, 1);
  793. }
  794. });
  795. if (selected) {
  796. new Toast("Successfully deselected playlist.");
  797. resolve();
  798. } else {
  799. new Toast("Playlist not selected.");
  800. resolve();
  801. }
  802. });
  803. },
  804. removeIncludedPlaylist(id) {
  805. return new Promise(resolve => {
  806. this.socket.dispatch(
  807. "stations.removeIncludedPlaylist",
  808. this.station._id,
  809. id,
  810. res => {
  811. new Toast(res.message);
  812. resolve();
  813. }
  814. );
  815. });
  816. },
  817. removeExcludedPlaylist(id) {
  818. return new Promise(resolve => {
  819. this.socket.dispatch(
  820. "stations.removeExcludedPlaylist",
  821. this.station._id,
  822. id,
  823. res => {
  824. new Toast(res.message);
  825. resolve();
  826. }
  827. );
  828. });
  829. },
  830. isSelected(id) {
  831. let selected = false;
  832. this.partyPlaylists.forEach(playlist => {
  833. if (playlist._id === id) selected = true;
  834. });
  835. return selected;
  836. },
  837. isIncluded(id) {
  838. let included = false;
  839. this.includedPlaylists.forEach(playlist => {
  840. if (playlist._id === id) included = true;
  841. });
  842. return included;
  843. },
  844. isExcluded(id) {
  845. let selected = false;
  846. this.excludedPlaylists.forEach(playlist => {
  847. if (playlist._id === id) selected = true;
  848. });
  849. return selected;
  850. },
  851. searchForPlaylists(page) {
  852. if (
  853. this.search.page >= page ||
  854. this.search.searchedQuery !== this.search.query
  855. ) {
  856. this.search.results = [];
  857. this.search.page = 0;
  858. this.search.count = 0;
  859. this.search.resultsLeft = 0;
  860. this.search.pageSize = 0;
  861. }
  862. const { query } = this.search;
  863. const action =
  864. this.station.type === "official"
  865. ? "playlists.searchOfficial"
  866. : "playlists.searchCommunity";
  867. this.search.searchedQuery = this.search.query;
  868. this.socket.dispatch(action, query, page, res => {
  869. const { data } = res;
  870. const { count, pageSize, playlists } = data;
  871. if (res.status === "success") {
  872. this.search.results = [
  873. ...this.search.results,
  874. ...playlists
  875. ];
  876. this.search.page = page;
  877. this.search.count = count;
  878. this.search.resultsLeft =
  879. count - this.search.results.length;
  880. this.search.pageSize = pageSize;
  881. } else if (res.status === "error") {
  882. this.search.results = [];
  883. this.search.page = 0;
  884. this.search.count = 0;
  885. this.search.resultsLeft = 0;
  886. this.search.pageSize = 0;
  887. new Toast(res.message);
  888. }
  889. });
  890. },
  891. async blacklistPlaylist(id) {
  892. if (this.isIncluded(id)) await this.removeIncludedPlaylist(id);
  893. this.socket.dispatch(
  894. "stations.excludePlaylist",
  895. this.station._id,
  896. id,
  897. res => {
  898. new Toast(res.message);
  899. }
  900. );
  901. },
  902. addPartyPlaylistSongToQueue() {
  903. let isInQueue = false;
  904. if (
  905. this.station.type === "community" &&
  906. this.station.partyMode === true
  907. ) {
  908. this.songsList.forEach(queueSong => {
  909. if (queueSong.requestedBy === this.userId) isInQueue = true;
  910. });
  911. if (!isInQueue && this.partyPlaylists) {
  912. const selectedPlaylist = this.partyPlaylists[
  913. Math.floor(Math.random() * this.partyPlaylists.length)
  914. ];
  915. if (
  916. selectedPlaylist._id &&
  917. selectedPlaylist.songs.length > 0
  918. ) {
  919. const selectedSong =
  920. selectedPlaylist.songs[
  921. Math.floor(
  922. Math.random() *
  923. selectedPlaylist.songs.length
  924. )
  925. ];
  926. if (selectedSong.youtubeId) {
  927. this.socket.dispatch(
  928. "stations.addToQueue",
  929. this.station._id,
  930. selectedSong.youtubeId,
  931. data => {
  932. if (data.status !== "success")
  933. new Toast("Error auto queueing song");
  934. }
  935. );
  936. }
  937. }
  938. }
  939. }
  940. },
  941. ...mapActions("station", ["updatePartyPlaylists"]),
  942. ...mapActions("modalVisibility", ["openModal"]),
  943. ...mapActions("user/playlists", ["editPlaylist", "setPlaylists"])
  944. }
  945. };
  946. </script>
  947. <style lang="scss" scoped>
  948. .excluded-icon {
  949. color: var(--red);
  950. }
  951. .included-icon {
  952. color: var(--green);
  953. }
  954. .selected-icon {
  955. color: var(--purple);
  956. }
  957. .station-playlists {
  958. .tabs-container {
  959. .tab-selection {
  960. display: flex;
  961. overflow-x: auto;
  962. .button {
  963. border-radius: 0;
  964. border: 0;
  965. text-transform: uppercase;
  966. font-size: 14px;
  967. color: var(--dark-grey-3);
  968. background-color: var(--light-grey-2);
  969. flex-grow: 1;
  970. height: 32px;
  971. &:not(:first-of-type) {
  972. margin-left: 5px;
  973. }
  974. }
  975. .selected {
  976. background-color: var(--primary-color) !important;
  977. color: var(--white) !important;
  978. font-weight: 600;
  979. }
  980. }
  981. .tab {
  982. padding: 15px 0;
  983. border-radius: 0;
  984. .playlist-item:not(:last-of-type),
  985. .item.item-draggable:not(:last-of-type) {
  986. margin-bottom: 10px;
  987. }
  988. .load-more-button {
  989. width: 100%;
  990. margin-top: 10px;
  991. }
  992. }
  993. }
  994. }
  995. .draggable-list-transition-move {
  996. transition: transform 0.5s;
  997. }
  998. .draggable-list-ghost {
  999. opacity: 0.5;
  1000. filter: brightness(95%);
  1001. }
  1002. </style>