Request.vue 9.4 KB

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