Request.vue 9.3 KB

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