Queue.vue 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. <template>
  2. <div id="queue">
  3. <div
  4. v-if="queue.length > 0"
  5. :class="{
  6. 'actionable-button-hidden': !actionableButtonVisible,
  7. 'scrollable-list': true
  8. }"
  9. >
  10. <draggable
  11. :component-data="{
  12. name: !drag ? 'draggable-list-transition' : null
  13. }"
  14. v-model="queue"
  15. item-key="_id"
  16. v-bind="dragOptions"
  17. @start="drag = true"
  18. @end="drag = false"
  19. @change="repositionSongInQueue"
  20. >
  21. <template #item="{ element, index }">
  22. <song-item
  23. :song="element"
  24. :requested-by="true"
  25. :class="{
  26. 'item-draggable': isAdminOnly() || isOwnerOnly()
  27. }"
  28. :disabled-actions="[]"
  29. :ref="`song-item-${index}`"
  30. >
  31. <template
  32. v-if="isAdminOnly() || isOwnerOnly()"
  33. #tippyActions
  34. >
  35. <quick-confirm
  36. v-if="isOwnerOnly() || isAdminOnly()"
  37. placement="left"
  38. @confirm="removeFromQueue(element.youtubeId)"
  39. >
  40. <i
  41. class="material-icons delete-icon"
  42. content="Remove Song from Queue"
  43. v-tippy
  44. >delete_forever</i
  45. >
  46. </quick-confirm>
  47. <i
  48. class="material-icons"
  49. v-if="index > 0"
  50. @click="moveSongToTop(element, index)"
  51. content="Move to top of Queue"
  52. v-tippy
  53. >vertical_align_top</i
  54. >
  55. <i
  56. v-if="queue.length - 1 !== index"
  57. @click="moveSongToBottom(element, index)"
  58. class="material-icons"
  59. content="Move to bottom of Queue"
  60. v-tippy
  61. >vertical_align_bottom</i
  62. >
  63. </template>
  64. </song-item>
  65. </template>
  66. </draggable>
  67. </div>
  68. <p class="nothing-here-text has-text-centered" v-else>
  69. There are no songs currently queued
  70. </p>
  71. </div>
  72. </template>
  73. <script>
  74. import { mapActions, mapState, mapGetters } from "vuex";
  75. import draggable from "vuedraggable";
  76. import Toast from "toasters";
  77. import SongItem from "@/components/SongItem.vue";
  78. export default {
  79. components: { draggable, SongItem },
  80. props: {
  81. modalUuid: { type: String, default: "" },
  82. sector: {
  83. type: String,
  84. default: "station"
  85. }
  86. },
  87. data() {
  88. return {
  89. actionableButtonVisible: false,
  90. drag: false
  91. };
  92. },
  93. computed: {
  94. station: {
  95. get() {
  96. if (this.sector === "manageStation")
  97. return this.$store.state.modals.manageStation[
  98. this.modalUuid
  99. ].station;
  100. return this.$store.state.station.station;
  101. },
  102. set(station) {
  103. if (this.sector === "manageStation")
  104. this.$store.commit(
  105. `modals/manageStation/${this.modalUuid}/updateStation`,
  106. station
  107. );
  108. else this.$store.commit("station/updateStation", station);
  109. }
  110. },
  111. queue: {
  112. get() {
  113. if (this.sector === "manageStation")
  114. return this.$store.state.modals.manageStation[
  115. this.modalUuid
  116. ].songsList;
  117. return this.$store.state.station.songsList;
  118. },
  119. set(queue) {
  120. if (this.sector === "manageStation")
  121. this.$store.commit(
  122. `modals/manageStation/${this.modalUuid}/updateSongsList`,
  123. queue
  124. );
  125. else this.$store.commit("station/updateSongsList", queue);
  126. }
  127. },
  128. dragOptions() {
  129. return {
  130. animation: 200,
  131. group: "queue",
  132. disabled: !(this.isAdminOnly() || this.isOwnerOnly()),
  133. ghostClass: "draggable-list-ghost"
  134. };
  135. },
  136. ...mapState({
  137. loggedIn: state => state.user.auth.loggedIn,
  138. userId: state => state.user.auth.userId,
  139. userRole: state => state.user.auth.role,
  140. noSong: state => state.station.noSong
  141. }),
  142. ...mapGetters({
  143. socket: "websockets/getSocket"
  144. })
  145. },
  146. updated() {
  147. // check if actionable button is visible, if not: set max-height of queue items to 100%
  148. if (
  149. document
  150. .getElementById("queue")
  151. .querySelectorAll(".tab-actionable-button").length > 0
  152. )
  153. this.actionableButtonVisible = true;
  154. else this.actionableButtonVisible = false;
  155. },
  156. methods: {
  157. isOwnerOnly() {
  158. return this.loggedIn && this.userId === this.station.owner;
  159. },
  160. isAdminOnly() {
  161. return this.loggedIn && this.userRole === "admin";
  162. },
  163. removeFromQueue(youtubeId) {
  164. this.socket.dispatch(
  165. "stations.removeFromQueue",
  166. this.station._id,
  167. youtubeId,
  168. res => {
  169. if (res.status === "success")
  170. new Toast("Successfully removed song from the queue.");
  171. else new Toast(res.message);
  172. }
  173. );
  174. },
  175. repositionSongInQueue({ moved }) {
  176. if (!moved) return; // we only need to update when song is moved
  177. this.socket.dispatch(
  178. "stations.repositionSongInQueue",
  179. this.station._id,
  180. {
  181. ...moved.element,
  182. oldIndex: moved.oldIndex,
  183. newIndex: moved.newIndex
  184. },
  185. res => {
  186. new Toast({ content: res.message, timeout: 4000 });
  187. if (res.status !== "success")
  188. this.repositionSongInList({
  189. ...moved.element,
  190. newIndex: moved.oldIndex,
  191. oldIndex: moved.newIndex
  192. });
  193. }
  194. );
  195. },
  196. moveSongToTop(song, index) {
  197. this.$refs[`song-item-${index}`].$refs.songActions.tippy.hide();
  198. this.repositionSongInQueue({
  199. moved: {
  200. element: song,
  201. oldIndex: index,
  202. newIndex: 0
  203. }
  204. });
  205. },
  206. moveSongToBottom(song, index) {
  207. this.$refs[`song-item-${index}`].$refs.songActions.tippy.hide();
  208. this.repositionSongInQueue({
  209. moved: {
  210. element: song,
  211. oldIndex: index,
  212. newIndex: this.queue.length
  213. }
  214. });
  215. },
  216. ...mapActions({
  217. repositionSongInList(dispatch, payload) {
  218. if (this.sector === "manageStation")
  219. return dispatch(
  220. "modals/manageStation/repositionSongInList",
  221. payload
  222. );
  223. return dispatch("station/repositionSongInList", payload);
  224. }
  225. }),
  226. ...mapActions("modalVisibility", ["openModal"]),
  227. ...mapActions({
  228. showManageStationTab: "modals/manageStation/showTab"
  229. })
  230. }
  231. };
  232. </script>
  233. <style lang="less" scoped>
  234. .night-mode {
  235. #queue {
  236. background-color: var(--dark-grey-3) !important;
  237. border: 0 !important;
  238. }
  239. }
  240. #queue {
  241. background-color: var(--white);
  242. border-radius: 0 0 @border-radius @border-radius;
  243. user-select: none;
  244. .actionable-button-hidden {
  245. max-height: 100%;
  246. }
  247. .song-item:not(:last-of-type) {
  248. margin-bottom: 10px;
  249. }
  250. #queue-locked {
  251. display: flex;
  252. justify-content: center;
  253. }
  254. button.disabled {
  255. filter: grayscale(0.4);
  256. }
  257. }
  258. </style>