AddSongToQueue.vue 8.6 KB

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