Queue.vue 5.8 KB

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