Queue.vue 7.4 KB

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