AddToPlaylistDropdown.vue 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. <script setup lang="ts">
  2. import { ref, onMounted } from "vue";
  3. import Toast from "toasters";
  4. import { storeToRefs } from "pinia";
  5. import {
  6. AddSongToPlaylistResponse,
  7. IndexMyPlaylistsResponse,
  8. RemoveSongFromPlaylistResponse
  9. } from "@musare_types/actions/PlaylistsActions";
  10. import { useWebsocketsStore } from "@/stores/websockets";
  11. import { useUserPlaylistsStore } from "@/stores/userPlaylists";
  12. import { useModalsStore } from "@/stores/modals";
  13. const props = defineProps({
  14. song: {
  15. type: Object,
  16. default: () => {}
  17. },
  18. placement: {
  19. type: String,
  20. default: "left"
  21. }
  22. });
  23. const emit = defineEmits(["showPlaylistDropdown"]);
  24. const dropdown = ref(null);
  25. const { socket } = useWebsocketsStore();
  26. const userPlaylistsStore = useUserPlaylistsStore();
  27. const { playlists } = storeToRefs(userPlaylistsStore);
  28. const { setPlaylists, addPlaylist, removePlaylist } = userPlaylistsStore;
  29. const { openModal } = useModalsStore();
  30. const hasSong = playlist =>
  31. playlist.songs.map(song => song.youtubeId).indexOf(props.song.youtubeId) !==
  32. -1;
  33. const toggleSongInPlaylist = playlistIndex => {
  34. const playlist = playlists.value[playlistIndex];
  35. if (!hasSong(playlist)) {
  36. socket.dispatch(
  37. "playlists.addSongToPlaylist",
  38. false,
  39. props.song.youtubeId,
  40. playlist._id,
  41. (res: AddSongToPlaylistResponse) => new Toast(res.message)
  42. );
  43. } else {
  44. socket.dispatch(
  45. "playlists.removeSongFromPlaylist",
  46. props.song.youtubeId,
  47. playlist._id,
  48. (res: RemoveSongFromPlaylistResponse) => new Toast(res.message)
  49. );
  50. }
  51. };
  52. const createPlaylist = () => {
  53. dropdown.value.tippy.setProps({
  54. zIndex: 0,
  55. hideOnClick: false
  56. });
  57. window.addToPlaylistDropdown = dropdown.value;
  58. openModal("createPlaylist");
  59. };
  60. onMounted(() => {
  61. socket.onConnect(() => {
  62. socket.dispatch(
  63. "playlists.indexMyPlaylists",
  64. (res: IndexMyPlaylistsResponse) => {
  65. if (res.status === "success") setPlaylists(res.data.playlists);
  66. }
  67. );
  68. });
  69. socket.on("event:playlist.created", res => addPlaylist(res.data.playlist), {
  70. replaceable: true
  71. });
  72. socket.on(
  73. "event:playlist.deleted",
  74. res => removePlaylist(res.data.playlistId),
  75. { replaceable: true }
  76. );
  77. socket.on(
  78. "event:playlist.displayName.updated",
  79. res => {
  80. playlists.value.forEach((playlist, index) => {
  81. if (playlist._id === res.data.playlistId) {
  82. playlists.value[index].displayName = res.data.displayName;
  83. }
  84. });
  85. },
  86. { replaceable: true }
  87. );
  88. });
  89. </script>
  90. <template>
  91. <tippy
  92. class="addToPlaylistDropdown"
  93. :touch="true"
  94. :interactive="true"
  95. :placement="placement"
  96. theme="dropdown"
  97. ref="dropdown"
  98. trigger="click"
  99. append-to="parent"
  100. @show="emit('showPlaylistDropdown', true)"
  101. @hide="emit('showPlaylistDropdown', false)"
  102. >
  103. <slot name="button" ref="trigger" />
  104. <template #content>
  105. <div class="nav-dropdown-items" v-if="playlists.length > 0">
  106. <button
  107. class="nav-item"
  108. v-for="(playlist, index) in playlists"
  109. :key="playlist._id"
  110. @click.prevent="toggleSongInPlaylist(index)"
  111. :title="playlist.displayName"
  112. >
  113. <p class="control is-expanded checkbox-control">
  114. <label class="switch">
  115. <input
  116. type="checkbox"
  117. :id="`${index}`"
  118. :checked="hasSong(playlist)"
  119. @click="toggleSongInPlaylist(index)"
  120. />
  121. <span class="slider round"></span>
  122. </label>
  123. <label :for="`${index}`">
  124. <span></span>
  125. <p>{{ playlist.displayName }}</p>
  126. </label>
  127. </p>
  128. </button>
  129. </div>
  130. <p v-else class="no-playlists">
  131. You haven't created any playlists.
  132. </p>
  133. <button
  134. id="create-playlist"
  135. class="button is-primary"
  136. @click="createPlaylist()"
  137. >
  138. <i class="material-icons icon-with-button"> edit </i>
  139. Create Playlist
  140. </button>
  141. </template>
  142. </tippy>
  143. </template>
  144. <style lang="less" scoped>
  145. .no-playlists {
  146. text-align: center;
  147. margin-top: 10px;
  148. }
  149. #create-playlist .material-icons {
  150. color: var(--white);
  151. }
  152. </style>