Queue.vue 5.3 KB

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