Queue.vue 6.0 KB

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