ConvertSpotifySongs.vue 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. <script setup lang="ts">
  2. import {
  3. defineProps,
  4. defineAsyncComponent,
  5. onMounted,
  6. ref,
  7. computed
  8. } from "vue";
  9. import Toast from "toasters";
  10. import { useModalsStore } from "@/stores/modals";
  11. import { useWebsocketsStore } from "@/stores/websockets";
  12. const Modal = defineAsyncComponent(() => import("@/components/Modal.vue"));
  13. const SongItem = defineAsyncComponent(
  14. () => import("@/components/SongItem.vue")
  15. );
  16. const { openModal, closeCurrentModal } = useModalsStore();
  17. const { socket } = useWebsocketsStore();
  18. const TAG = "CSS";
  19. const props = defineProps({
  20. modalUuid: { type: String, required: true },
  21. playlistId: { type: String, default: null }
  22. });
  23. const playlist = ref(null);
  24. const allSongs = ref(null);
  25. const loaded = ref(false);
  26. const currentConvertType = ref("artist");
  27. const sortBy = ref("track_count_des");
  28. const spotifyArtists = ref({});
  29. const spotifyArtistsArray = computed(() =>
  30. Object.entries(spotifyArtists.value)
  31. .map(([spotifyArtistId, spotifyArtist]) => ({
  32. artistId: spotifyArtistId,
  33. ...spotifyArtist
  34. }))
  35. .sort((a, b) => {
  36. if (sortBy.value === "track_count_des")
  37. return b.songs.length - a.songs.length;
  38. if (sortBy.value === "track_count_asc")
  39. return a.songs.length - b.songs.length;
  40. })
  41. );
  42. const toggleSpotifyArtistExpanded = spotifyArtistId => {
  43. spotifyArtists.value[spotifyArtistId].expanded =
  44. !spotifyArtists.value[spotifyArtistId].expanded;
  45. };
  46. onMounted(() => {
  47. console.debug(TAG, "On mounted start");
  48. console.debug(TAG, "Getting playlist", props);
  49. socket.dispatch("playlists.getPlaylist", props.playlistId, res => {
  50. console.debug(TAG, "Get playlist response", res);
  51. if (res.status !== "success") {
  52. new Toast(res.message);
  53. closeCurrentModal();
  54. return;
  55. }
  56. playlist.value = res.data.playlist;
  57. allSongs.value = {};
  58. playlist.value.songs
  59. .filter(song => song.mediaSource.startsWith("spotify:"))
  60. .forEach(song => {
  61. allSongs.value[song.mediaSource] = {
  62. song,
  63. track: null
  64. };
  65. });
  66. const mediaSources = Object.keys(allSongs.value);
  67. console.debug(TAG, "getTracksFromMediaSources start", mediaSources);
  68. socket.dispatch(
  69. "spotify.getTracksFromMediaSources",
  70. mediaSources,
  71. res => {
  72. console.debug(TAG, "getTracksFromMediaSources response", res);
  73. if (res.status !== "success") {
  74. new Toast(res.message);
  75. closeCurrentModal();
  76. return;
  77. }
  78. const { tracks } = res.data;
  79. Object.entries(tracks).forEach(([mediaSource, track]) => {
  80. allSongs.value[mediaSource].track = track;
  81. track.artistIds.forEach((artistId, artistIndex) => {
  82. if (!spotifyArtists.value[artistId]) {
  83. spotifyArtists.value[artistId] = {
  84. name: track.artists[artistIndex],
  85. songs: [mediaSource],
  86. expanded: false
  87. };
  88. } else
  89. spotifyArtists.value[artistId].songs.push(
  90. mediaSource
  91. );
  92. });
  93. });
  94. loaded.value = true;
  95. }
  96. );
  97. });
  98. console.debug(TAG, "On mounted end");
  99. });
  100. </script>
  101. <template>
  102. <div>
  103. <modal
  104. title="Convert Spotify Songs"
  105. class="convert-spotify-songs-modal"
  106. size="wide"
  107. @closed="closeCurrentModal()"
  108. >
  109. <template #body>
  110. <p>Converting by {{ currentConvertType }}</p>
  111. <p>Sorting by {{ sortBy }}</p>
  112. <br />
  113. <div class="column-headers">
  114. <div class="spotify-column-header column-header">
  115. <h3>Spotify</h3>
  116. </div>
  117. <div class="soumdcloud-column-header column-header">
  118. <h3>Soundcloud</h3>
  119. </div>
  120. </div>
  121. <div class="artists">
  122. <div
  123. v-for="spotifyArtist in spotifyArtistsArray"
  124. :key="spotifyArtist.artistId"
  125. class="artist-item"
  126. >
  127. <div class="spotify-section">
  128. <p
  129. @click="
  130. toggleSpotifyArtistExpanded(
  131. spotifyArtist.artistId
  132. )
  133. "
  134. >
  135. {{ spotifyArtist.name }} ({{
  136. spotifyArtist.songs.length
  137. }}
  138. songs)
  139. </p>
  140. <div
  141. class="spotify-songs"
  142. v-if="spotifyArtist.expanded"
  143. >
  144. <div
  145. v-for="mediaSource in spotifyArtist.songs"
  146. :key="`${spotifyArtist.artistId}-${mediaSource}`"
  147. class="spotify-song"
  148. >
  149. <song-item
  150. :song="{
  151. title: allSongs[mediaSource].track
  152. .name,
  153. duration:
  154. allSongs[mediaSource].track
  155. .duration,
  156. artists:
  157. allSongs[mediaSource].track
  158. .artists,
  159. thumbnail:
  160. allSongs[mediaSource].track
  161. .albumImageUrl
  162. }"
  163. :disabled-actions="[
  164. 'youtube',
  165. 'report',
  166. 'addToPlaylist',
  167. 'edit'
  168. ]"
  169. ></song-item>
  170. </div>
  171. </div>
  172. </div>
  173. <div class="soundcloud-section">
  174. <p>Not found</p>
  175. <div v-if="spotifyArtist.expanded">
  176. <button class="button">Get artist</button>
  177. </div>
  178. </div>
  179. </div>
  180. </div>
  181. </template>
  182. </modal>
  183. </div>
  184. </template>
  185. <style lang="less" scoped>
  186. .column-headers {
  187. display: flex;
  188. flex-direction: row;
  189. .column-header {
  190. flex: 1;
  191. }
  192. }
  193. .artists {
  194. display: flex;
  195. flex-direction: column;
  196. .artist-item {
  197. display: flex;
  198. flex-direction: column;
  199. row-gap: 8px;
  200. box-shadow: inset 0px 0px 1px white;
  201. width: 50%;
  202. position: relative;
  203. .spotify-section {
  204. display: flex;
  205. flex-direction: column;
  206. row-gap: 8px;
  207. padding: 8px 12px;
  208. .spotify-songs {
  209. display: flex;
  210. flex-direction: column;
  211. row-gap: 4px;
  212. }
  213. }
  214. .soundcloud-section {
  215. position: absolute;
  216. left: 100%;
  217. top: 0;
  218. width: 100%;
  219. height: 100%;
  220. overflow: hidden;
  221. box-shadow: inset 0px 0px 1px white;
  222. padding: 8px 12px;
  223. }
  224. }
  225. }
  226. </style>