Queue.vue 7.6 KB

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