Playlists.vue 24 KB

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