PlaylistTabBase.vue 25 KB

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