AddSongToQueue.vue 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. <template>
  2. <modal title="Add Song To Queue">
  3. <div slot="body">
  4. <div class="vertical-padding">
  5. <!-- Choosing a song from youtube -->
  6. <h4 class="section-title">Choose a song</h4>
  7. <p class="section-description">
  8. Choose a song by searching or using a link from YouTube.
  9. </p>
  10. <br />
  11. <div class="control is-grouped input-with-button">
  12. <p class="control is-expanded">
  13. <input
  14. class="input"
  15. type="text"
  16. placeholder="Enter your YouTube query here..."
  17. v-model="search.songs.query"
  18. autofocus
  19. @keyup.enter="searchForSongs()"
  20. />
  21. </p>
  22. <p class="control">
  23. <a
  24. class="button is-info"
  25. @click.prevent="searchForSongs()"
  26. href="#"
  27. ><i class="material-icons icon-with-button"
  28. >search</i
  29. >Search</a
  30. >
  31. </p>
  32. </div>
  33. <!-- Choosing a song from youtube - query results -->
  34. <div
  35. id="song-query-results"
  36. v-if="search.songs.results.length > 0"
  37. >
  38. <search-query-item
  39. v-for="(result, index) in search.songs.results"
  40. :key="index"
  41. :result="result"
  42. >
  43. <div slot="actions">
  44. <transition
  45. name="search-query-actions"
  46. mode="out-in"
  47. >
  48. <a
  49. class="button is-success"
  50. v-if="result.isAddedToQueue"
  51. href="#"
  52. key="added-to-playlist"
  53. >
  54. <i class="material-icons icon-with-button"
  55. >done</i
  56. >
  57. Added to queue
  58. </a>
  59. <a
  60. class="button is-dark"
  61. v-else
  62. @click.prevent="
  63. addSongToQueue(result.id, index)
  64. "
  65. href="#"
  66. key="add-to-queue"
  67. >
  68. <i class="material-icons icon-with-button"
  69. >add</i
  70. >
  71. Add to queue
  72. </a>
  73. </transition>
  74. </div>
  75. </search-query-item>
  76. <a
  77. class="button is-default load-more-button"
  78. @click.prevent="loadMoreSongs()"
  79. href="#"
  80. >
  81. Load more...
  82. </a>
  83. </div>
  84. <!-- Import a playlist from youtube -->
  85. <div v-if="station.type === 'official'">
  86. <hr class="section-horizontal-rule" />
  87. <h4 class="section-title">Import a playlist</h4>
  88. <p class="section-description">
  89. Import a playlist by using a link from YouTube.
  90. </p>
  91. <br />
  92. <div class="control is-grouped input-with-button">
  93. <p class="control is-expanded">
  94. <input
  95. class="input"
  96. type="text"
  97. placeholder="YouTube Playlist URL"
  98. v-model="search.playlist.query"
  99. @keyup.enter="importPlaylist()"
  100. />
  101. </p>
  102. <p class="control has-addons">
  103. <span class="select" id="playlist-import-type">
  104. <select
  105. v-model="
  106. search.playlist.isImportingOnlyMusic
  107. "
  108. >
  109. <option :value="false">Import all</option>
  110. <option :value="true">
  111. Import only music
  112. </option>
  113. </select>
  114. </span>
  115. <a
  116. class="button is-info"
  117. @click.prevent="importPlaylist()"
  118. href="#"
  119. ><i class="material-icons icon-with-button"
  120. >publish</i
  121. >Import</a
  122. >
  123. </p>
  124. </div>
  125. </div>
  126. <!-- Choose a playlist from your account -->
  127. <div
  128. v-if="
  129. loggedIn &&
  130. station.type === 'community' &&
  131. playlists.length > 0
  132. "
  133. >
  134. <hr class="section-horizontal-rule" />
  135. <aside id="playlist-to-queue-selection">
  136. <h4 class="section-title">Choose a playlist</h4>
  137. <p class="section-description">
  138. Choose one of your playlists to add to the queue.
  139. </p>
  140. <br />
  141. <div id="playlists">
  142. <div
  143. class="playlist"
  144. v-for="(playlist, index) in playlists"
  145. :key="index"
  146. >
  147. <playlist-item :playlist="playlist">
  148. <div slot="actions">
  149. <a
  150. class="button is-danger"
  151. href="#"
  152. @click.prevent="
  153. togglePlaylistSelection(
  154. playlist._id
  155. )
  156. "
  157. v-if="
  158. isPlaylistSelected(playlist._id)
  159. "
  160. >
  161. <i
  162. class="material-icons icon-with-button"
  163. >stop</i
  164. >
  165. Stop playing
  166. </a>
  167. <a
  168. class="button is-success"
  169. @click.prevent="
  170. togglePlaylistSelection(
  171. playlist._id
  172. )
  173. "
  174. href="#"
  175. v-else
  176. ><i
  177. class="material-icons icon-with-button"
  178. >play_arrow</i
  179. >Play in queue
  180. </a>
  181. </div>
  182. </playlist-item>
  183. </div>
  184. </div>
  185. </aside>
  186. </div>
  187. </div>
  188. </div>
  189. </modal>
  190. </template>
  191. <script>
  192. import { mapState, mapGetters, mapActions } from "vuex";
  193. import Toast from "toasters";
  194. import SearchYoutube from "@/mixins/SearchYoutube.vue";
  195. import PlaylistItem from "../PlaylistItem.vue";
  196. import SearchQueryItem from "../SearchQueryItem.vue";
  197. import Modal from "../Modal.vue";
  198. export default {
  199. components: { Modal, PlaylistItem, SearchQueryItem },
  200. mixins: [SearchYoutube],
  201. data() {
  202. return {
  203. playlists: []
  204. };
  205. },
  206. computed: {
  207. ...mapState({
  208. loggedIn: state => state.user.auth.loggedIn,
  209. station: state => state.station.station,
  210. privatePlaylistQueueSelected: state =>
  211. state.station.privatePlaylistQueueSelected
  212. }),
  213. ...mapGetters({
  214. socket: "websockets/getSocket"
  215. })
  216. },
  217. mounted() {
  218. this.socket.dispatch("playlists.indexMyPlaylists", true, res => {
  219. if (res.status === "success") this.playlists = res.data.playlists;
  220. });
  221. },
  222. methods: {
  223. isPlaylistSelected(playlistId) {
  224. return this.privatePlaylistQueueSelected === playlistId;
  225. },
  226. togglePlaylistSelection(playlistId) {
  227. if (this.station.type === "community") {
  228. if (this.isPlaylistSelected(playlistId)) {
  229. this.updatePrivatePlaylistQueueSelected(null);
  230. } else {
  231. this.updatePrivatePlaylistQueueSelected(playlistId);
  232. this.$parent.addFirstPrivatePlaylistSongToQueue();
  233. console.log(this.isPlaylistSelected(playlistId));
  234. }
  235. }
  236. },
  237. addSongToQueue(youtubeId, index) {
  238. if (this.station.type === "community") {
  239. this.socket.dispatch(
  240. "stations.addToQueue",
  241. this.station._id,
  242. youtubeId,
  243. res => {
  244. if (res.status !== "success")
  245. new Toast(`Error: ${res.message}`);
  246. else {
  247. this.search.songs.results[
  248. index
  249. ].isAddedToQueue = true;
  250. new Toast(res.message);
  251. }
  252. }
  253. );
  254. } else {
  255. this.socket.dispatch("songs.request", youtubeId, res => {
  256. if (res.status !== "success")
  257. new Toast(`Error: ${res.message}`);
  258. else {
  259. this.search.songs.results[index].isAddedToQueue = true;
  260. new Toast(res.message);
  261. }
  262. });
  263. }
  264. },
  265. importPlaylist() {
  266. let isImportingPlaylist = true;
  267. // import query is blank
  268. if (!this.search.playlist.query)
  269. return new Toast("Please enter a YouTube playlist URL.");
  270. const regex = new RegExp(`[\\?&]list=([^&#]*)`);
  271. const splitQuery = regex.exec(this.search.playlist.query);
  272. if (!splitQuery) {
  273. return new Toast({
  274. content: "Please enter a valid YouTube playlist URL.",
  275. timeout: 4000
  276. });
  277. }
  278. // don't give starting import message instantly in case of instant error
  279. setTimeout(() => {
  280. if (isImportingPlaylist) {
  281. new Toast(
  282. "Starting to import your playlist. This can take some time to do."
  283. );
  284. }
  285. }, 750);
  286. return this.socket.dispatch(
  287. "songs.requestSet",
  288. this.search.playlist.query,
  289. this.search.playlist.isImportingOnlyMusic,
  290. res => {
  291. isImportingPlaylist = false;
  292. return new Toast({ content: res.message, timeout: 20000 });
  293. }
  294. );
  295. },
  296. ...mapActions("station", ["updatePrivatePlaylistQueueSelected"]),
  297. ...mapActions("user/playlists", ["editPlaylist"])
  298. }
  299. };
  300. </script>
  301. <style lang="scss" scoped>
  302. .night-mode {
  303. div {
  304. color: var(--dark-grey);
  305. }
  306. }
  307. .song-actions {
  308. .button {
  309. height: 36px;
  310. width: 140px;
  311. }
  312. }
  313. .song-thumbnail div {
  314. width: 96px;
  315. height: 54px;
  316. background-position: center;
  317. background-repeat: no-repeat;
  318. }
  319. .table {
  320. margin-bottom: 0;
  321. margin-top: 20px;
  322. }
  323. #playlist-to-queue-selection {
  324. margin-top: 0;
  325. #playlists {
  326. font-size: 18px;
  327. .playlist {
  328. .button {
  329. width: 150px;
  330. }
  331. i {
  332. color: var(--white);
  333. }
  334. }
  335. .playlist:not(:last-of-type) {
  336. margin-bottom: 10px;
  337. }
  338. .radio {
  339. display: flex;
  340. flex-direction: row;
  341. align-items: center;
  342. input {
  343. transform: scale(1.25);
  344. }
  345. }
  346. }
  347. }
  348. #playlist-import-type {
  349. &:hover {
  350. z-index: initial;
  351. }
  352. select {
  353. border-radius: 0;
  354. }
  355. }
  356. .vertical-padding {
  357. padding: 20px;
  358. }
  359. #song-query-results {
  360. padding: 10px;
  361. max-height: 500px;
  362. overflow: auto;
  363. border: 1px solid var(--light-grey-3);
  364. border-radius: 3px;
  365. .search-query-item:not(:last-of-type) {
  366. margin-bottom: 10px;
  367. }
  368. .load-more-button {
  369. width: 100%;
  370. margin-top: 10px;
  371. }
  372. }
  373. </style>