1
0

Queue.vue 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. <script setup lang="ts">
  2. import { defineAsyncComponent, ref, computed, onUpdated } from "vue";
  3. import { Sortable } from "sortablejs-vue3";
  4. import Toast from "toasters";
  5. import { useWebsocketsStore } from "@/stores/websockets";
  6. import { useStationStore } from "@/stores/station";
  7. import { useManageStationStore } from "@/stores/manageStation";
  8. const SongItem = defineAsyncComponent(
  9. () => import("@/components/SongItem.vue")
  10. );
  11. const props = defineProps({
  12. modalUuid: { type: String, default: "" },
  13. sector: { type: String, default: "station" }
  14. });
  15. const { socket } = useWebsocketsStore();
  16. const stationStore = useStationStore();
  17. const manageStationStore = useManageStationStore(props);
  18. const repositionSongInList = payload => {
  19. if (props.sector === "manageStation")
  20. return manageStationStore.repositionSongInList(payload);
  21. return stationStore.repositionSongInList(payload);
  22. };
  23. const actionableButtonVisible = ref(false);
  24. const drag = ref(false);
  25. const songItems = ref([]);
  26. const station = computed({
  27. get: () => {
  28. if (props.sector === "manageStation") return manageStationStore.station;
  29. return stationStore.station;
  30. },
  31. set: value => {
  32. if (props.sector === "manageStation")
  33. manageStationStore.updateStation(value);
  34. else stationStore.updateStation(value);
  35. }
  36. });
  37. const queue = computed({
  38. get: () => {
  39. if (props.sector === "manageStation")
  40. return manageStationStore.songsList;
  41. return stationStore.songsList;
  42. },
  43. set: value => {
  44. if (props.sector === "manageStation")
  45. manageStationStore.updateSongsList(value);
  46. else stationStore.updateSongsList(value);
  47. }
  48. });
  49. const hasPermission = permission =>
  50. props.sector === "manageStation"
  51. ? manageStationStore.hasPermission(permission)
  52. : stationStore.hasPermission(permission);
  53. const dragOptions = computed(() => ({
  54. animation: 200,
  55. group: "queue",
  56. disabled: !hasPermission("stations.repositionSongInQueue"),
  57. ghostClass: "draggable-list-ghost"
  58. }));
  59. const removeFromQueue = youtubeId => {
  60. socket.dispatch(
  61. "stations.removeFromQueue",
  62. station.value._id,
  63. youtubeId,
  64. res => {
  65. if (res.status === "success")
  66. new Toast("Successfully removed song from the queue.");
  67. else new Toast(res.message);
  68. }
  69. );
  70. };
  71. const repositionSongInQueue = ({ oldIndex, newIndex }) => {
  72. if (oldIndex === newIndex) return; // we only need to update when song is moved
  73. const song = queue.value[oldIndex];
  74. socket.dispatch(
  75. "stations.repositionSongInQueue",
  76. station.value._id,
  77. {
  78. ...song,
  79. oldIndex,
  80. newIndex
  81. },
  82. res => {
  83. new Toast({ content: res.message, timeout: 4000 });
  84. if (res.status !== "success")
  85. repositionSongInList({
  86. ...song,
  87. oldIndex,
  88. newIndex
  89. });
  90. }
  91. );
  92. };
  93. const moveSongToTop = index => {
  94. songItems.value[`song-item-${index}`].$refs.songActions.tippy.hide();
  95. repositionSongInQueue({
  96. oldIndex: index,
  97. newIndex: 0
  98. });
  99. };
  100. const moveSongToBottom = index => {
  101. songItems.value[`song-item-${index}`].$refs.songActions.tippy.hide();
  102. repositionSongInQueue({
  103. oldIndex: index,
  104. newIndex: queue.value.length
  105. });
  106. };
  107. onUpdated(() => {
  108. // check if actionable button is visible, if not: set max-height of queue items to 100%
  109. actionableButtonVisible.value =
  110. document
  111. .getElementById("queue")
  112. .querySelectorAll(".tab-actionable-button").length > 0;
  113. });
  114. </script>
  115. <template>
  116. <div id="queue">
  117. <div
  118. v-if="queue.length > 0"
  119. :class="{
  120. 'actionable-button-hidden': !actionableButtonVisible,
  121. 'scrollable-list': true
  122. }"
  123. >
  124. <Sortable
  125. :component-data="{
  126. name: !drag ? 'draggable-list-transition' : null
  127. }"
  128. :list="queue"
  129. item-key="_id"
  130. :options="dragOptions"
  131. @start="drag = true"
  132. @end="drag = false"
  133. @update="repositionSongInQueue"
  134. >
  135. <template #item="{ element, index }">
  136. <song-item
  137. :song="element"
  138. :requested-by="true"
  139. :class="{
  140. 'item-draggable': hasPermission(
  141. 'stations.repositionSongInQueue'
  142. )
  143. }"
  144. :disabled-actions="[]"
  145. :ref="el => (songItems[`song-item-${index}`] = el)"
  146. >
  147. <template
  148. v-if="
  149. hasPermission('stations.repositionSongInQueue')
  150. "
  151. #tippyActions
  152. >
  153. <quick-confirm
  154. v-if="hasPermission('stations.removeFromQueue')"
  155. placement="left"
  156. @confirm="removeFromQueue(element.youtubeId)"
  157. >
  158. <i
  159. class="material-icons delete-icon"
  160. content="Remove Song from Queue"
  161. v-tippy
  162. >delete_forever</i
  163. >
  164. </quick-confirm>
  165. <i
  166. class="material-icons"
  167. v-if="index > 0"
  168. @click="moveSongToTop(index)"
  169. content="Move to top of Queue"
  170. v-tippy
  171. >vertical_align_top</i
  172. >
  173. <i
  174. v-if="queue.length - 1 !== index"
  175. @click="moveSongToBottom(index)"
  176. class="material-icons"
  177. content="Move to bottom of Queue"
  178. v-tippy
  179. >vertical_align_bottom</i
  180. >
  181. </template>
  182. </song-item>
  183. </template>
  184. </Sortable>
  185. </div>
  186. <p class="nothing-here-text has-text-centered" v-else>
  187. There are no songs currently queued
  188. </p>
  189. </div>
  190. </template>
  191. <style lang="less" scoped>
  192. .night-mode {
  193. #queue {
  194. background-color: var(--dark-grey-3) !important;
  195. border: 0 !important;
  196. }
  197. }
  198. #queue {
  199. background-color: var(--white);
  200. border-radius: 0 0 @border-radius @border-radius;
  201. user-select: none;
  202. .actionable-button-hidden {
  203. max-height: 100%;
  204. }
  205. .song-item:not(:last-of-type) {
  206. margin-bottom: 10px;
  207. }
  208. #queue-locked {
  209. display: flex;
  210. justify-content: center;
  211. }
  212. button.disabled {
  213. filter: grayscale(0.4);
  214. }
  215. }
  216. </style>