Queue.vue 6.1 KB

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