Request.vue 26 KB

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