History.vue 4.5 KB

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