BulkEditPlaylist.vue 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. <script setup lang="ts">
  2. import { reactive, computed, defineAsyncComponent } from "vue";
  3. import Toast from "toasters";
  4. import { useWebsocketsStore } from "@/stores/websockets";
  5. import { useLongJobsStore } from "@/stores/longJobs";
  6. import { useModalsStore } from "@/stores/modals";
  7. const Modal = defineAsyncComponent(() => import("@/components/Modal.vue"));
  8. const PlaylistItem = defineAsyncComponent(
  9. () => import("@/components/PlaylistItem.vue")
  10. );
  11. const QuickConfirm = defineAsyncComponent(
  12. () => import("@/components/QuickConfirm.vue")
  13. );
  14. const props = defineProps({
  15. modalUuid: { type: String, required: true },
  16. mediaSources: { type: Array, required: true }
  17. });
  18. const { closeCurrentModal } = useModalsStore();
  19. const { setJob } = useLongJobsStore();
  20. const { socket } = useWebsocketsStore();
  21. const { openModal } = useModalsStore();
  22. const search = reactive({
  23. query: "",
  24. searchedQuery: "",
  25. page: 0,
  26. count: 0,
  27. resultsLeft: 0,
  28. pageSize: 0,
  29. results: []
  30. });
  31. const resultsLeftCount = computed(() => search.count - search.results.length);
  32. const nextPageResultsCount = computed(() =>
  33. Math.min(search.pageSize, resultsLeftCount.value)
  34. );
  35. const searchForPlaylists = page => {
  36. if (search.page >= page || search.searchedQuery !== search.query) {
  37. search.results = [];
  38. search.page = 0;
  39. search.count = 0;
  40. search.resultsLeft = 0;
  41. search.pageSize = 0;
  42. }
  43. const { query } = search;
  44. const action = "playlists.searchAdmin";
  45. search.searchedQuery = search.query;
  46. socket.dispatch(action, query, page, res => {
  47. const { data } = res;
  48. if (res.status === "success") {
  49. const { count, pageSize, playlists } = data;
  50. search.results = [...search.results, ...playlists];
  51. search.page = page;
  52. search.count = count;
  53. search.resultsLeft = count - search.results.length;
  54. search.pageSize = pageSize;
  55. } else if (res.status === "error") {
  56. search.results = [];
  57. search.page = 0;
  58. search.count = 0;
  59. search.resultsLeft = 0;
  60. search.pageSize = 0;
  61. new Toast(res.message);
  62. }
  63. });
  64. };
  65. const addSongsToPlaylist = playlistId => {
  66. let id;
  67. let title;
  68. socket.dispatch(
  69. "playlists.addSongsToPlaylist",
  70. playlistId,
  71. props.mediaSources,
  72. {
  73. cb: () => {},
  74. onProgress: res => {
  75. if (res.status === "started") {
  76. id = res.id;
  77. title = res.title;
  78. closeCurrentModal();
  79. }
  80. if (id)
  81. setJob({
  82. id,
  83. name: title,
  84. ...res
  85. });
  86. }
  87. }
  88. );
  89. };
  90. const removeSongsFromPlaylist = playlistId => {
  91. let id;
  92. let title;
  93. socket.dispatch(
  94. "playlists.removeSongsFromPlaylist",
  95. playlistId,
  96. props.mediaSources,
  97. {
  98. cb: data => {
  99. console.log("FINISHED", data);
  100. },
  101. onProgress: res => {
  102. if (res.status === "started") {
  103. id = res.id;
  104. title = res.title;
  105. closeCurrentModal();
  106. }
  107. if (id)
  108. setJob({
  109. id,
  110. name: title,
  111. ...res
  112. });
  113. }
  114. }
  115. );
  116. };
  117. </script>
  118. <template>
  119. <div>
  120. <modal
  121. title="Bulk Edit Playlist"
  122. class="bulk-edit-playlist-modal"
  123. size="slim"
  124. >
  125. <template #body>
  126. <div>
  127. <label class="label">Search for a playlist</label>
  128. <div class="control is-grouped input-with-button">
  129. <p class="control is-expanded">
  130. <input
  131. class="input"
  132. type="text"
  133. placeholder="Enter your playlist query here..."
  134. v-model="search.query"
  135. @keyup.enter="searchForPlaylists(1)"
  136. />
  137. </p>
  138. <p class="control">
  139. <a
  140. class="button is-info"
  141. @click="searchForPlaylists(1)"
  142. ><i class="material-icons icon-with-button"
  143. >search</i
  144. >Search</a
  145. >
  146. </p>
  147. </div>
  148. <div v-if="search.results.length > 0">
  149. <playlist-item
  150. v-for="playlist in search.results"
  151. :key="`searchKey-${playlist._id}`"
  152. :playlist="playlist"
  153. :show-owner="true"
  154. >
  155. <template #actions>
  156. <quick-confirm
  157. @confirm="addSongsToPlaylist(playlist._id)"
  158. >
  159. <i
  160. class="material-icons add-to-playlist-icon"
  161. :content="`Add songs to playlist`"
  162. v-tippy
  163. >
  164. playlist_add
  165. </i>
  166. </quick-confirm>
  167. <quick-confirm
  168. @confirm="
  169. removeSongsFromPlaylist(playlist._id)
  170. "
  171. >
  172. <i
  173. class="material-icons remove-from-playlist-icon"
  174. :content="`Remove songs from playlist`"
  175. v-tippy
  176. >
  177. playlist_remove
  178. </i>
  179. </quick-confirm>
  180. <i
  181. @click="
  182. openModal({
  183. modal: 'editPlaylist',
  184. props: { playlistId: playlist._id }
  185. })
  186. "
  187. class="material-icons edit-icon"
  188. content="Edit Playlist"
  189. v-tippy
  190. >edit</i
  191. >
  192. </template>
  193. </playlist-item>
  194. <button
  195. v-if="resultsLeftCount > 0"
  196. class="button is-primary load-more-button"
  197. @click="searchForPlaylists(search.page + 1)"
  198. >
  199. Load {{ nextPageResultsCount }} more results
  200. </button>
  201. </div>
  202. </div>
  203. </template>
  204. </modal>
  205. </div>
  206. </template>
  207. <style lang="less" scoped>
  208. .label {
  209. text-transform: capitalize;
  210. }
  211. .playlist-item:not(:last-of-type) {
  212. margin-bottom: 10px;
  213. }
  214. .load-more-button {
  215. width: 100%;
  216. margin-top: 10px;
  217. }
  218. </style>