Queue.vue 7.5 KB

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