AddToPlaylistDropdown.vue 3.7 KB

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