AddToPlaylistDropdown.vue 3.7 KB

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