Queue.vue 7.4 KB

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