AddSongToQueue.vue 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  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="modal-section-title">Choose a song</h4>
  7. <p class="modal-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="querySearch"
  18. autofocus
  19. @keyup.enter="submitQuery()"
  20. />
  21. </p>
  22. <p class="control">
  23. <a
  24. class="button is-info"
  25. v-on:click="submitQuery()"
  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. <table
  35. class="table"
  36. style="margin-top: 20px;"
  37. v-if="queryResults.length > 0"
  38. >
  39. <tbody>
  40. <tr
  41. v-for="(result, index) in queryResults"
  42. :key="index"
  43. >
  44. <td class="song-thumbnail">
  45. <div
  46. :style="
  47. `background-image: url('${result.thumbnail}'`
  48. "
  49. ></div>
  50. </td>
  51. <td><strong v-html="result.title"></strong></td>
  52. <td class="song-actions">
  53. <transition
  54. name="song-actions-transition"
  55. mode="out-in"
  56. >
  57. <a
  58. class="button is-success"
  59. v-if="result.isAddedToQueue"
  60. href="#"
  61. key="added-to-queue"
  62. >
  63. <i
  64. class="material-icons icon-with-button"
  65. >done</i
  66. >
  67. Added to queue
  68. </a>
  69. <a
  70. class="button is-dark"
  71. v-else
  72. v-on:click="
  73. addSongToQueue(result.id, index)
  74. "
  75. href="#"
  76. key="add-to-queue"
  77. >
  78. <i
  79. class="material-icons icon-with-button"
  80. >add</i
  81. >
  82. Add to queue
  83. </a>
  84. </transition>
  85. </td>
  86. </tr>
  87. </tbody>
  88. </table>
  89. <!-- Import a playlist from youtube -->
  90. <hr style="margin: 30px 0;" />
  91. <h4 class="modal-section-title">
  92. Import a playlist
  93. </h4>
  94. <p class="modal-section-description">
  95. Import a playlist by using a link from YouTube.
  96. </p>
  97. <br />
  98. <div
  99. class="control is-grouped input-with-button"
  100. v-if="station.type === 'official'"
  101. >
  102. <p class="control is-expanded">
  103. <input
  104. class="input"
  105. type="text"
  106. placeholder="YouTube Playlist URL"
  107. v-model="importQuery"
  108. @keyup.enter="importPlaylist()"
  109. />
  110. </p>
  111. <p class="control">
  112. <a
  113. class="button is-info"
  114. v-on:click="importPlaylist()"
  115. href="#"
  116. ><i class="material-icons icon-with-button"
  117. >publish</i
  118. >Import</a
  119. >
  120. </p>
  121. </div>
  122. <!-- Choose a playlist from your account -->
  123. <div
  124. v-if="
  125. loggedIn &&
  126. station.type === 'community' &&
  127. playlists.length > 0
  128. "
  129. >
  130. <hr style="margin: 30px 0;" />
  131. <aside id="playlist-to-queue-selection">
  132. <h4 class="modal-section-title">Choose a playlist</h4>
  133. <p class="modal-section-description">
  134. Choose one of your playlists to add to the queue.
  135. </p>
  136. <br />
  137. <div id="playlists">
  138. <div
  139. class="playlist"
  140. v-for="(playlist, index) in playlists"
  141. :key="index"
  142. >
  143. <playlist-item :playlist="playlist">
  144. <div slot="actions">
  145. <a
  146. class="button is-danger"
  147. v-on:click="
  148. addSongToQueue(result.id)
  149. "
  150. href="#"
  151. @click="
  152. togglePlaylistSelection(
  153. playlist._id
  154. )
  155. "
  156. v-if="
  157. isPlaylistSelected(playlist._id)
  158. "
  159. >
  160. <i
  161. class="material-icons icon-with-button"
  162. >stop</i
  163. >
  164. Stop playing
  165. </a>
  166. <a
  167. class="button is-success"
  168. v-on:click="
  169. addSongToQueue(result.id)
  170. "
  171. href="#"
  172. @click="
  173. togglePlaylistSelection(
  174. playlist._id
  175. )
  176. "
  177. v-else
  178. ><i
  179. class="material-icons icon-with-button"
  180. >play_arrow</i
  181. >Play in queue
  182. </a>
  183. </div>
  184. </playlist-item>
  185. </div>
  186. </div>
  187. </aside>
  188. </div>
  189. </div>
  190. </div>
  191. </modal>
  192. </template>
  193. <script>
  194. import { mapState, mapActions } from "vuex";
  195. import Toast from "toasters";
  196. import PlaylistItem from "../PlaylistItem.vue";
  197. import Modal from "./Modal.vue";
  198. import io from "../../io";
  199. export default {
  200. data() {
  201. return {
  202. querySearch: "",
  203. queryResults: [],
  204. playlists: [],
  205. importQuery: ""
  206. };
  207. },
  208. computed: mapState({
  209. loggedIn: state => state.user.auth.loggedIn,
  210. station: state => state.station.station,
  211. privatePlaylistQueueSelected: state =>
  212. state.station.privatePlaylistQueueSelected
  213. }),
  214. methods: {
  215. isPlaylistSelected(playlistId) {
  216. return this.privatePlaylistQueueSelected === playlistId;
  217. },
  218. togglePlaylistSelection(playlistId) {
  219. if (this.station.type === "community") {
  220. if (this.isPlaylistSelected(playlistId)) {
  221. this.updatePrivatePlaylistQueueSelected(null);
  222. } else {
  223. this.updatePrivatePlaylistQueueSelected(playlistId);
  224. this.$parent.addFirstPrivatePlaylistSongToQueue();
  225. console.log(this.isPlaylistSelected(playlistId));
  226. }
  227. }
  228. },
  229. addSongToQueue(songId, index) {
  230. if (this.station.type === "community") {
  231. this.socket.emit(
  232. "stations.addToQueue",
  233. this.station._id,
  234. songId,
  235. data => {
  236. if (data.status !== "success")
  237. new Toast({
  238. content: `Error: ${data.message}`,
  239. timeout: 8000
  240. });
  241. else {
  242. this.queryResults[index].isAddedToQueue = true;
  243. new Toast({
  244. content: `${data.message}`,
  245. timeout: 4000
  246. });
  247. }
  248. }
  249. );
  250. } else {
  251. this.socket.emit("queueSongs.add", songId, data => {
  252. if (data.status !== "success")
  253. new Toast({
  254. content: `Error: ${data.message}`,
  255. timeout: 8000
  256. });
  257. else {
  258. this.queryResults[index].isAddedToQueue = true;
  259. new Toast({
  260. content: `${data.message}`,
  261. timeout: 4000
  262. });
  263. }
  264. });
  265. }
  266. },
  267. importPlaylist() {
  268. new Toast({
  269. content:
  270. "Starting to import your playlist. This can take some time to do.",
  271. timeout: 4000
  272. });
  273. this.socket.emit(
  274. "queueSongs.addSetToQueue",
  275. this.importQuery,
  276. res => {
  277. return new Toast({ content: res.message, timeout: 4000 });
  278. }
  279. );
  280. },
  281. submitQuery() {
  282. let query = this.querySearch;
  283. if (!this.querySearch)
  284. return new Toast({
  285. content: "Please input a search query or a YouTube link",
  286. timeout: 4000
  287. });
  288. if (query.indexOf("&index=") !== -1) {
  289. query = query.split("&index=");
  290. query.pop();
  291. query = query.join("");
  292. }
  293. if (query.indexOf("&list=") !== -1) {
  294. query = query.split("&list=");
  295. query.pop();
  296. query = query.join("");
  297. }
  298. return this.socket.emit("apis.searchYoutube", query, res => {
  299. if (res.status === "failure")
  300. return new Toast({
  301. content: "Error searching on YouTube",
  302. timeout: 4000
  303. });
  304. const { data } = res;
  305. this.queryResults = [];
  306. console.log(res.data);
  307. for (let i = 0; i < data.items.length; i += 1) {
  308. this.queryResults.push({
  309. id: data.items[i].id.videoId,
  310. url: `https://www.youtube.com/watch?v=${this.id}`,
  311. title: data.items[i].snippet.title,
  312. thumbnail: data.items[i].snippet.thumbnails.default.url,
  313. isAddedToQueue: false
  314. });
  315. }
  316. return this.queryResults;
  317. });
  318. },
  319. ...mapActions("station", ["updatePrivatePlaylistQueueSelected"]),
  320. ...mapActions("user/playlists", ["editPlaylist"])
  321. },
  322. mounted() {
  323. io.getSocket(socket => {
  324. this.socket = socket;
  325. this.socket.emit("playlists.indexForUser", res => {
  326. if (res.status === "success") this.playlists = res.data;
  327. });
  328. });
  329. },
  330. components: { Modal, PlaylistItem }
  331. };
  332. </script>
  333. <style lang="scss" scoped>
  334. @import "styles/global.scss";
  335. tr td {
  336. vertical-align: middle;
  337. }
  338. .song-thumbnail {
  339. padding-left: 0;
  340. }
  341. .song-actions {
  342. .button {
  343. height: 36px;
  344. width: 140px;
  345. }
  346. }
  347. .song-thumbnail div {
  348. width: 96px;
  349. height: 54px;
  350. background-position: center;
  351. background-repeat: no-repeat;
  352. }
  353. .table {
  354. margin-bottom: 0;
  355. }
  356. .night-mode {
  357. div {
  358. color: #4d4d4d;
  359. }
  360. }
  361. #playlist-to-queue-selection {
  362. margin-top: 0;
  363. #playlists {
  364. font-size: 18px;
  365. .radio {
  366. display: flex;
  367. flex-direction: row;
  368. align-items: center;
  369. input {
  370. transform: scale(1.25);
  371. }
  372. }
  373. }
  374. }
  375. .input-with-button {
  376. .control {
  377. margin-right: 0px !important;
  378. }
  379. input {
  380. height: 36px;
  381. border-radius: 3px 0 3px 0;
  382. }
  383. .button {
  384. height: 36px;
  385. border-radius: 0 3px 3px 0;
  386. }
  387. }
  388. .vertical-padding {
  389. padding: 20px;
  390. }
  391. #playlists {
  392. .playlist {
  393. .button {
  394. width: 146px;
  395. }
  396. }
  397. }
  398. .song-actions-transition-enter-active {
  399. transition: all 0.2s ease;
  400. }
  401. .song-actions-transition-leave-active {
  402. transition: all 0.2s cubic-bezier(1, 0.5, 0.8, 1);
  403. }
  404. .song-actions-transition-enter {
  405. transform: translateX(-20px);
  406. opacity: 0;
  407. }
  408. .song-actions-transition-leave-to {
  409. transform: translateX(20px);
  410. opacity: 0;
  411. }
  412. </style>