Queue.vue 7.5 KB

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