Request.vue 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. <template>
  2. <div class="station-playlists">
  3. <p class="top-info has-text-centered">
  4. Add songs to the queue or automatically request songs from playlists
  5. </p>
  6. <div class="tabs-container">
  7. <div class="tab-selection">
  8. <button
  9. class="button is-default"
  10. ref="songs-tab"
  11. :class="{ selected: tab === 'songs' }"
  12. @click="showTab('songs')"
  13. >
  14. Songs
  15. </button>
  16. <button
  17. v-if="!disableAutoRequest"
  18. class="button is-default"
  19. ref="autorequest-tab"
  20. :class="{ selected: tab === 'autorequest' }"
  21. @click="showTab('autorequest')"
  22. >
  23. Autorequest
  24. </button>
  25. <button
  26. v-else
  27. class="button is-default disabled"
  28. content="Only available on station pages"
  29. v-tippy
  30. >
  31. Autorequest
  32. </button>
  33. </div>
  34. <div class="tab" v-show="tab === 'songs'">
  35. <div class="musare-songs">
  36. <label class="label">
  37. Search for a song on {{ sitename }}
  38. </label>
  39. <div class="control is-grouped input-with-button">
  40. <p class="control is-expanded">
  41. <input
  42. class="input"
  43. type="text"
  44. placeholder="Enter your song query here..."
  45. v-model="musareSearch.query"
  46. @keyup.enter="searchForMusareSongs(1)"
  47. />
  48. </p>
  49. <p class="control">
  50. <a
  51. class="button is-info"
  52. @click="searchForMusareSongs(1)"
  53. ><i class="material-icons icon-with-button"
  54. >search</i
  55. >Search</a
  56. >
  57. </p>
  58. </div>
  59. <div v-if="musareSearch.results.length > 0">
  60. <song-item
  61. v-for="song in musareSearch.results"
  62. :key="song._id"
  63. :song="song"
  64. >
  65. <template #actions>
  66. <transition
  67. name="musare-search-query-actions"
  68. mode="out-in"
  69. >
  70. <i
  71. v-if="
  72. songsInQueue.indexOf(
  73. song.youtubeId
  74. ) !== -1
  75. "
  76. class="material-icons added-to-playlist-icon"
  77. content="Song is already in queue"
  78. v-tippy
  79. >done</i
  80. >
  81. <i
  82. v-else
  83. class="material-icons add-to-queue-icon"
  84. @click="addSongToQueue(song.youtubeId)"
  85. content="Add Song to Queue"
  86. v-tippy
  87. >queue</i
  88. >
  89. </transition>
  90. </template>
  91. </song-item>
  92. <button
  93. v-if="musareResultsLeftCount > 0"
  94. class="button is-primary load-more-button"
  95. @click="searchForMusareSongs(musareSearch.page + 1)"
  96. >
  97. Load {{ nextPageMusareResultsCount }} more 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. <playlist-tab-base
  172. v-if="!disableAutoRequest"
  173. class="tab"
  174. v-show="tab === 'autorequest'"
  175. :type="'autorequest'"
  176. :sector="sector"
  177. :modal-uuid="modalUuid"
  178. />
  179. </div>
  180. </div>
  181. </template>
  182. <script>
  183. import { mapState, mapGetters } from "vuex";
  184. import Toast from "toasters";
  185. import SongItem from "@/components/SongItem.vue";
  186. import SearchQueryItem from "@/components/SearchQueryItem.vue";
  187. import PlaylistTabBase from "@/components/PlaylistTabBase.vue";
  188. import SearchYoutube from "@/mixins/SearchYoutube.vue";
  189. import SearchMusare from "@/mixins/SearchMusare.vue";
  190. export default {
  191. components: {
  192. SongItem,
  193. SearchQueryItem,
  194. PlaylistTabBase
  195. },
  196. mixins: [SearchYoutube, SearchMusare],
  197. props: {
  198. modalUuid: { type: String, default: "" },
  199. sector: { type: String, default: "station" },
  200. disableAutoRequest: { type: Boolean, default: false }
  201. },
  202. data() {
  203. return {
  204. tab: "songs",
  205. sitename: "Musare"
  206. };
  207. },
  208. computed: {
  209. station: {
  210. get() {
  211. if (this.sector === "manageStation")
  212. return this.$store.state.modals.manageStation[
  213. this.modalUuid
  214. ].station;
  215. return this.$store.state.station.station;
  216. },
  217. set(station) {
  218. if (this.sector === "manageStation")
  219. this.$store.commit(
  220. `modals/manageStation/${this.modalUuid}/updateStation`,
  221. station
  222. );
  223. else this.$store.commit("station/updateStation", station);
  224. }
  225. },
  226. blacklist: {
  227. get() {
  228. if (this.sector === "manageStation")
  229. return this.$store.state.modals.manageStation[
  230. this.modalUuid
  231. ].blacklist;
  232. return this.$store.state.station.blacklist;
  233. },
  234. set(blacklist) {
  235. if (this.sector === "manageStation")
  236. this.$store.commit(
  237. `modals/manageStation/${this.modalUuid}/setBlacklist`,
  238. blacklist
  239. );
  240. else this.$store.commit("station/setBlacklist", blacklist);
  241. }
  242. },
  243. songsList: {
  244. get() {
  245. if (this.sector === "manageStation")
  246. return this.$store.state.modals.manageStation[
  247. this.modalUuid
  248. ].songsList;
  249. return this.$store.state.station.songsList;
  250. },
  251. set(songsList) {
  252. if (this.sector === "manageStation")
  253. this.$store.commit(
  254. `modals/manageStation/${this.modalUuid}/updateSongsList`,
  255. songsList
  256. );
  257. else this.$store.commit("station/updateSongsList", songsList);
  258. }
  259. },
  260. musareResultsLeftCount() {
  261. return this.musareSearch.count - this.musareSearch.results.length;
  262. },
  263. nextPageMusareResultsCount() {
  264. return Math.min(
  265. this.musareSearch.pageSize,
  266. this.musareResultsLeftCount
  267. );
  268. },
  269. songsInQueue() {
  270. if (this.station.currentSong)
  271. return this.songsList
  272. .map(song => song.youtubeId)
  273. .concat(this.station.currentSong.youtubeId);
  274. return this.songsList.map(song => song.youtubeId);
  275. },
  276. currentUserQueueSongs() {
  277. return this.songsList.filter(
  278. queueSong => queueSong.requestedBy === this.userId
  279. ).length;
  280. },
  281. ...mapState("user", {
  282. loggedIn: state => state.auth.loggedIn,
  283. role: state => state.auth.role,
  284. userId: state => state.auth.userId
  285. }),
  286. ...mapGetters({
  287. socket: "websockets/getSocket"
  288. })
  289. },
  290. async mounted() {
  291. this.sitename = await lofig.get("siteSettings.sitename");
  292. this.showTab("songs");
  293. },
  294. methods: {
  295. showTab(tab) {
  296. this.$refs[`${tab}-tab`].scrollIntoView({ block: "nearest" });
  297. this.tab = tab;
  298. },
  299. addSongToQueue(youtubeId, index) {
  300. this.socket.dispatch(
  301. "stations.addToQueue",
  302. this.station._id,
  303. youtubeId,
  304. res => {
  305. if (res.status !== "success")
  306. new Toast(`Error: ${res.message}`);
  307. else {
  308. if (index)
  309. this.youtubeSearch.songs.results[
  310. index
  311. ].isAddedToQueue = true;
  312. new Toast(res.message);
  313. }
  314. }
  315. );
  316. }
  317. }
  318. };
  319. </script>
  320. <style lang="less" scoped>
  321. .night-mode {
  322. .tabs-container .tab-selection .button {
  323. background: var(--dark-grey) !important;
  324. color: var(--white) !important;
  325. }
  326. }
  327. :deep(#create-new-playlist-button) {
  328. width: 100%;
  329. }
  330. .station-playlists {
  331. .top-info {
  332. font-size: 15px;
  333. margin-bottom: 15px;
  334. }
  335. .tabs-container {
  336. .tab-selection {
  337. display: flex;
  338. overflow-x: auto;
  339. .button {
  340. border-radius: 0;
  341. border: 0;
  342. text-transform: uppercase;
  343. font-size: 14px;
  344. color: var(--dark-grey-3);
  345. background-color: var(--light-grey-2);
  346. flex-grow: 1;
  347. height: 32px;
  348. &:not(:first-of-type) {
  349. margin-left: 5px;
  350. }
  351. }
  352. .selected {
  353. background-color: var(--primary-color) !important;
  354. color: var(--white) !important;
  355. font-weight: 600;
  356. }
  357. }
  358. .tab {
  359. padding: 10px 0;
  360. border-radius: 0;
  361. .item.item-draggable:not(:last-of-type) {
  362. margin-bottom: 10px;
  363. }
  364. .load-more-button {
  365. width: 100%;
  366. margin-top: 10px;
  367. }
  368. }
  369. }
  370. }
  371. .youtube-search {
  372. margin-top: 10px;
  373. .search-query-item:not(:last-of-type) {
  374. margin-bottom: 10px;
  375. }
  376. }
  377. </style>