Playlists.vue 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. <template>
  2. <div>
  3. <page-metadata title="Admin | Playlists" />
  4. <div class="admin-tab">
  5. <div class="button-row">
  6. <run-job-dropdown :jobs="jobs" />
  7. </div>
  8. <advanced-table
  9. :column-default="columnDefault"
  10. :columns="columns"
  11. :filters="filters"
  12. data-action="playlists.getData"
  13. name="admin-playlists"
  14. :events="events"
  15. >
  16. <template #column-options="slotProps">
  17. <div class="row-options">
  18. <button
  19. class="
  20. button
  21. is-primary
  22. icon-with-button
  23. material-icons
  24. "
  25. @click="edit(slotProps.item._id)"
  26. :disabled="slotProps.item.removed"
  27. content="Edit Playlist"
  28. v-tippy
  29. >
  30. edit
  31. </button>
  32. </div>
  33. </template>
  34. <template #column-displayName="slotProps">
  35. <span :title="slotProps.item.displayName">{{
  36. slotProps.item.displayName
  37. }}</span>
  38. </template>
  39. <template #column-type="slotProps">
  40. <span :title="slotProps.item.type">{{
  41. slotProps.item.type
  42. }}</span>
  43. </template>
  44. <template #column-privacy="slotProps">
  45. <span :title="slotProps.item.privacy">{{
  46. slotProps.item.privacy
  47. }}</span>
  48. </template>
  49. <template #column-songsCount="slotProps">
  50. <span :title="slotProps.item.songsCount">{{
  51. slotProps.item.songsCount
  52. }}</span>
  53. </template>
  54. <template #column-totalLength="slotProps">
  55. <span :title="formatTimeLong(slotProps.item.totalLength)">{{
  56. formatTimeLong(slotProps.item.totalLength)
  57. }}</span>
  58. </template>
  59. <template #column-createdBy="slotProps">
  60. <span v-if="slotProps.item.createdBy === 'Musare'"
  61. >Musare</span
  62. >
  63. <user-id-to-username
  64. v-else
  65. :user-id="slotProps.item.createdBy"
  66. :link="true"
  67. />
  68. </template>
  69. <template #column-createdAt="slotProps">
  70. <span :title="new Date(slotProps.item.createdAt)">{{
  71. getDateFormatted(slotProps.item.createdAt)
  72. }}</span>
  73. </template>
  74. <template #column-createdFor="slotProps">
  75. <span :title="slotProps.item.createdFor">{{
  76. slotProps.item.createdFor
  77. }}</span>
  78. </template>
  79. <template #column-_id="slotProps">
  80. <span :title="slotProps.item._id">{{
  81. slotProps.item._id
  82. }}</span>
  83. </template>
  84. </advanced-table>
  85. </div>
  86. <edit-playlist v-if="modals.editPlaylist" sector="admin" />
  87. <edit-song v-if="modals.editSong" song-type="songs" />
  88. <report v-if="modals.report" />
  89. </div>
  90. </template>
  91. <script>
  92. import { mapState, mapActions, mapGetters } from "vuex";
  93. import { defineAsyncComponent } from "vue";
  94. import AdvancedTable from "@/components/AdvancedTable.vue";
  95. import RunJobDropdown from "@/components/RunJobDropdown.vue";
  96. import UserIdToUsername from "@/components/UserIdToUsername.vue";
  97. import utils from "../../../../js/utils";
  98. export default {
  99. components: {
  100. EditPlaylist: defineAsyncComponent(() =>
  101. import("@/components/modals/EditPlaylist")
  102. ),
  103. Report: defineAsyncComponent(() =>
  104. import("@/components/modals/Report.vue")
  105. ),
  106. EditSong: defineAsyncComponent(() =>
  107. import("@/components/modals/EditSong")
  108. ),
  109. AdvancedTable,
  110. RunJobDropdown,
  111. UserIdToUsername
  112. },
  113. data() {
  114. return {
  115. utils,
  116. columnDefault: {
  117. sortable: true,
  118. hidable: true,
  119. defaultVisibility: "shown",
  120. draggable: true,
  121. resizable: true,
  122. minWidth: 150,
  123. maxWidth: 600
  124. },
  125. columns: [
  126. {
  127. name: "options",
  128. displayName: "Options",
  129. properties: ["_id"],
  130. sortable: false,
  131. hidable: false,
  132. resizable: false,
  133. minWidth: 76,
  134. defaultWidth: 76
  135. },
  136. {
  137. name: "displayName",
  138. displayName: "Display Name",
  139. properties: ["displayName"],
  140. sortProperty: "displayName"
  141. },
  142. {
  143. name: "type",
  144. displayName: "Type",
  145. properties: ["type"],
  146. sortProperty: "type"
  147. },
  148. {
  149. name: "privacy",
  150. displayName: "Privacy",
  151. properties: ["privacy"],
  152. sortProperty: "privacy"
  153. },
  154. {
  155. name: "songsCount",
  156. displayName: "Songs #",
  157. properties: ["songsCount"],
  158. sortProperty: "songsCount",
  159. minWidth: 100,
  160. defaultWidth: 100
  161. },
  162. {
  163. name: "totalLength",
  164. displayName: "Total Length",
  165. properties: ["totalLength"],
  166. sortProperty: "totalLength",
  167. minWidth: 250,
  168. defaultWidth: 250
  169. },
  170. {
  171. name: "createdBy",
  172. displayName: "Created By",
  173. properties: ["createdBy"],
  174. sortProperty: "createdBy",
  175. defaultWidth: 150
  176. },
  177. {
  178. name: "createdAt",
  179. displayName: "Created At",
  180. properties: ["createdAt"],
  181. sortProperty: "createdAt",
  182. defaultWidth: 150
  183. },
  184. {
  185. name: "createdFor",
  186. displayName: "Created For",
  187. properties: ["createdFor"],
  188. sortProperty: "createdFor",
  189. minWidth: 230,
  190. defaultWidth: 230
  191. },
  192. {
  193. name: "_id",
  194. displayName: "Playlist ID",
  195. properties: ["_id"],
  196. sortProperty: "_id",
  197. minWidth: 230,
  198. defaultWidth: 230
  199. }
  200. ],
  201. filters: [
  202. {
  203. name: "_id",
  204. displayName: "Playlist ID",
  205. property: "_id",
  206. filterTypes: ["exact"],
  207. defaultFilterType: "exact"
  208. },
  209. {
  210. name: "displayName",
  211. displayName: "Display Name",
  212. property: "displayName",
  213. filterTypes: ["contains", "exact", "regex"],
  214. defaultFilterType: "contains"
  215. },
  216. {
  217. name: "type",
  218. displayName: "Type",
  219. property: "type",
  220. filterTypes: ["exact", "regex"],
  221. defaultFilterType: "exact",
  222. filterValues: [
  223. "genre",
  224. "station",
  225. "user",
  226. "user-disliked",
  227. "user-liked"
  228. ]
  229. },
  230. {
  231. name: "privacy",
  232. displayName: "Privacy",
  233. property: "privacy",
  234. filterTypes: ["exact", "regex"],
  235. defaultFilterType: "exact",
  236. filterValues: ["public", "private"]
  237. },
  238. {
  239. name: "songsCount",
  240. displayName: "Songs Count",
  241. property: "songsCount",
  242. filterTypes: [
  243. "numberLesserEqual",
  244. "numberLesser",
  245. "numberGreater",
  246. "numberGreaterEqual",
  247. "numberEquals",
  248. "exact",
  249. "regex"
  250. ],
  251. defaultFilterType: "numberLesser"
  252. },
  253. {
  254. name: "totalLength",
  255. displayName: "Total Length",
  256. property: "totalLength",
  257. filterTypes: [
  258. "numberLesserEqual",
  259. "numberLesser",
  260. "numberGreater",
  261. "numberGreaterEqual",
  262. "numberEquals",
  263. "exact",
  264. "regex"
  265. ],
  266. defaultFilterType: "numberLesser"
  267. },
  268. {
  269. name: "createdBy",
  270. displayName: "Created By",
  271. property: "createdBy",
  272. filterTypes: ["contains", "exact", "regex"],
  273. defaultFilterType: "contains"
  274. },
  275. {
  276. name: "createdAt",
  277. displayName: "Created At",
  278. property: "createdAt",
  279. filterTypes: ["datetimeBefore", "datetimeAfter"],
  280. defaultFilterType: "datetimeBefore"
  281. },
  282. {
  283. name: "createdFor",
  284. displayName: "Created For",
  285. property: "createdFor",
  286. filterTypes: ["contains", "exact", "regex"],
  287. defaultFilterType: "contains"
  288. }
  289. ],
  290. events: {
  291. adminRoom: "playlists",
  292. removed: {
  293. event: "admin.playlist.deleted",
  294. id: "playlistId"
  295. }
  296. },
  297. jobs: [
  298. {
  299. name: "Delete orphaned station playlists",
  300. socket: "playlists.deleteOrphanedStationPlaylists"
  301. },
  302. {
  303. name: "Delete orphaned genre playlists",
  304. socket: "playlists.deleteOrphanedGenrePlaylists"
  305. },
  306. {
  307. name: "Request orphaned playlist songs",
  308. socket: "playlists.requestOrphanedPlaylistSongs"
  309. },
  310. {
  311. name: "Clear and refill all station playlists",
  312. socket: "playlists.clearAndRefillAllStationPlaylists"
  313. },
  314. {
  315. name: "Clear and refill all genre playlists",
  316. socket: "playlists.clearAndRefillAllGenrePlaylists"
  317. },
  318. {
  319. name: "Create missing genre playlists",
  320. socket: "playlists.createMissingGenrePlaylists"
  321. }
  322. ]
  323. };
  324. },
  325. computed: {
  326. ...mapState("modalVisibility", {
  327. modals: state => state.modals
  328. }),
  329. ...mapGetters({
  330. socket: "websockets/getSocket"
  331. })
  332. },
  333. mounted() {
  334. // TODO
  335. // this.socket.on("event:admin.playlist.song.added", res =>
  336. // this.addPlaylistSong({
  337. // playlistId: res.data.playlistId,
  338. // song: res.data.song
  339. // })
  340. // );
  341. // this.socket.on("event:admin.playlist.song.removed", res =>
  342. // this.removePlaylistSong({
  343. // playlistId: res.data.playlistId,
  344. // youtubeId: res.data.youtubeId
  345. // })
  346. // );
  347. // this.socket.on("event:admin.playlist.displayName.updated", res =>
  348. // this.updatePlaylistDisplayName({
  349. // playlistId: res.data.playlistId,
  350. // displayName: res.data.displayName
  351. // })
  352. // );
  353. // this.socket.on("event:admin.playlist.privacy.updated", res =>
  354. // this.updatePlaylistPrivacy({
  355. // playlistId: res.data.playlistId,
  356. // privacy: res.data.privacy
  357. // })
  358. // );
  359. },
  360. methods: {
  361. edit(playlistId) {
  362. this.editPlaylist(playlistId);
  363. this.openModal("editPlaylist");
  364. },
  365. getDateFormatted(createdAt) {
  366. const date = new Date(createdAt);
  367. const year = date.getFullYear();
  368. const month = `${date.getMonth() + 1}`.padStart(2, 0);
  369. const day = `${date.getDate()}`.padStart(2, 0);
  370. const hour = `${date.getHours()}`.padStart(2, 0);
  371. const minute = `${date.getMinutes()}`.padStart(2, 0);
  372. return `${year}-${month}-${day} ${hour}:${minute}`;
  373. },
  374. formatTimeLong(length) {
  375. return this.utils.formatTimeLong(length);
  376. },
  377. ...mapActions("modalVisibility", ["openModal"]),
  378. ...mapActions("user/playlists", ["editPlaylist"])
  379. }
  380. };
  381. </script>