History.vue 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. <script setup lang="ts">
  2. import { computed, onMounted } from "vue";
  3. import Toast from "toasters";
  4. import { storeToRefs } from "pinia";
  5. import { useWebsocketsStore } from "@/stores/websockets";
  6. import { useStationStore } from "@/stores/station";
  7. import MediaItem from "@/components/MediaItem.vue";
  8. import { useConfigStore } from "@/stores/config";
  9. import { useUserAuthStore } from "@/stores/userAuth";
  10. const stationStore = useStationStore();
  11. const { socket } = useWebsocketsStore();
  12. const { history } = storeToRefs(stationStore);
  13. const configStore = useConfigStore();
  14. const { experimental } = storeToRefs(configStore);
  15. const userAuthStore = useUserAuthStore();
  16. const { loggedIn, userId, role: userRole } = storeToRefs(userAuthStore);
  17. const station = computed({
  18. get() {
  19. return stationStore.station;
  20. },
  21. set(value) {
  22. stationStore.updateStation(value);
  23. }
  24. });
  25. const songsList = computed({
  26. get() {
  27. return stationStore.songsList;
  28. },
  29. set(value) {
  30. stationStore.updateSongsList(value);
  31. }
  32. });
  33. const songsInQueue = computed(() => {
  34. if (station.value.currentSong)
  35. return songsList.value
  36. .map(song => song.mediaSource)
  37. .concat(station.value.currentSong.mediaSource);
  38. return songsList.value.map(song => song.mediaSource);
  39. });
  40. const formatDate = dateString => {
  41. const skippedAtDate = new Date(dateString);
  42. const now = new Date();
  43. const time = `${skippedAtDate
  44. .getHours()
  45. .toString()
  46. .padStart(2, "0")}:${skippedAtDate
  47. .getMinutes()
  48. .toString()
  49. .padStart(2, "0")}`;
  50. const date = `${skippedAtDate.getFullYear()}-${(
  51. skippedAtDate.getMonth() + 1
  52. )
  53. .toString()
  54. .padStart(2, "0")}-${skippedAtDate
  55. .getDate()
  56. .toString()
  57. .padStart(2, "0")}`;
  58. if (skippedAtDate.toLocaleDateString() === now.toLocaleDateString()) {
  59. return time;
  60. }
  61. return `${date} ${time}`;
  62. };
  63. const formatSkipReason = skipReason => {
  64. if (skipReason === "natural") return "";
  65. if (skipReason === "other") return " - automatically skipped";
  66. if (skipReason === "vote_skip") return " - vote skipped";
  67. if (skipReason === "force_skip") return " - force skipped";
  68. return "";
  69. };
  70. const addSongToQueue = (mediaSource: string) => {
  71. socket.dispatch(
  72. "stations.addToQueue",
  73. station.value._id,
  74. mediaSource,
  75. "manual",
  76. res => {
  77. if (res.status !== "success") new Toast(`Error: ${res.message}`);
  78. else {
  79. new Toast(res.message);
  80. }
  81. }
  82. );
  83. };
  84. onMounted(async () => {});
  85. </script>
  86. <template>
  87. <div class="station-history">
  88. <div v-for="historyItem in history" :key="historyItem._id">
  89. <media-item
  90. :song="historyItem.payload.song"
  91. :requested-by="true"
  92. :header="`Finished playing at ${formatDate(
  93. historyItem.payload.skippedAt
  94. )}${formatSkipReason(historyItem.payload.skipReason)}`"
  95. >
  96. <template
  97. v-if="
  98. loggedIn &&
  99. station &&
  100. station.requests &&
  101. station.requests.enabled &&
  102. (station.requests.access === 'user' ||
  103. (station.requests.access === 'owner' &&
  104. (userRole === 'admin' ||
  105. station.owner === userId))) &&
  106. (!historyItem.payload.song.mediaSource.startsWith(
  107. 'soundcloud:'
  108. ) ||
  109. experimental.soundcloud)
  110. "
  111. #actions
  112. >
  113. <transition
  114. name="musare-history-query-actions"
  115. mode="out-in"
  116. >
  117. <i
  118. v-if="
  119. songsInQueue.indexOf(
  120. historyItem.payload.song.mediaSource
  121. ) !== -1
  122. "
  123. class="material-icons disabled"
  124. content="Song is already in queue"
  125. v-tippy
  126. >queue</i
  127. >
  128. <i
  129. v-else
  130. class="material-icons add-to-queue-icon"
  131. @click="
  132. addSongToQueue(
  133. historyItem.payload.song.mediaSource
  134. )
  135. "
  136. content="Add Song to Queue"
  137. v-tippy
  138. >queue</i
  139. >
  140. </transition>
  141. </template>
  142. </media-item>
  143. </div>
  144. </div>
  145. </template>
  146. <style lang="less" scoped>
  147. .night-mode {
  148. .station-history {
  149. background-color: var(--dark-grey-3) !important;
  150. border: 0 !important;
  151. }
  152. }
  153. .station-history {
  154. background-color: var(--white);
  155. margin-bottom: 20px;
  156. border-radius: 0 0 @border-radius @border-radius;
  157. max-height: 100%;
  158. padding: 12px;
  159. overflow: auto;
  160. row-gap: 8px;
  161. display: flex;
  162. flex-direction: column;
  163. h1 {
  164. margin: 0;
  165. }
  166. .disabled {
  167. cursor: not-allowed;
  168. }
  169. :deep(.song-item) {
  170. min-height: 90px !important;
  171. height: 100% !important;
  172. .thumbnail-and-info .thumbnail {
  173. min-width: 90px;
  174. width: 90px;
  175. }
  176. }
  177. }
  178. </style>