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