EditSongs.vue 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. <template>
  2. <edit-song
  3. :bulk="true"
  4. v-if="!closed && currentSong"
  5. @savedSuccess="onSavedSuccess"
  6. @savedError="onSavedError"
  7. @saving="onSaving"
  8. @toggleFlag="toggleFlag"
  9. @nextSong="editNextSong"
  10. >
  11. <template v-if="items.length > 1" #sidebar>
  12. <div class="sidebar">
  13. <header class="sidebar-head">
  14. <h2 class="sidebar-title is-marginless">Edit Queue</h2>
  15. <!-- <span class="delete material-icons" @click="closeCurrentModal()"
  16. >highlight_off</span
  17. > -->
  18. </header>
  19. <section class="sidebar-body">
  20. <div
  21. class="item"
  22. v-for="(
  23. { status, flagged, song }, index
  24. ) in filteredItems"
  25. :key="song._id"
  26. >
  27. <song-item
  28. :song="song"
  29. :thumbnail="false"
  30. :duration="false"
  31. :disabled-actions="
  32. song.removed ? ['all'] : ['report', 'edit']
  33. "
  34. :class="{
  35. updated: song.updated,
  36. removed: song.removed
  37. }"
  38. >
  39. <template #leftIcon>
  40. <i
  41. v-if="currentSong._id === song._id"
  42. class="
  43. material-icons
  44. item-icon
  45. editing-icon
  46. "
  47. content="Currently editing song"
  48. v-tippy="{ theme: 'info' }"
  49. >edit</i
  50. >
  51. <i
  52. v-else-if="song.removed"
  53. class="
  54. material-icons
  55. item-icon
  56. removed-icon
  57. "
  58. content="Song removed"
  59. v-tippy="{ theme: 'info' }"
  60. >delete_forever</i
  61. >
  62. <i
  63. v-else-if="status === 'error'"
  64. class="material-icons item-icon error-icon"
  65. content="Error saving song"
  66. v-tippy="{ theme: 'info' }"
  67. >error</i
  68. >
  69. <i
  70. v-else-if="status === 'saving'"
  71. class="material-icons item-icon saving-icon"
  72. content="Currently saving song"
  73. v-tippy="{ theme: 'info' }"
  74. >pending</i
  75. >
  76. <i
  77. v-else-if="flagged"
  78. class="material-icons item-icon flag-icon"
  79. content="Song flagged"
  80. v-tippy="{ theme: 'info' }"
  81. >flag_circle</i
  82. >
  83. <i
  84. v-else-if="status === 'done'"
  85. class="material-icons item-icon done-icon"
  86. content="Song marked complete"
  87. v-tippy="{ theme: 'info' }"
  88. >check_circle</i
  89. >
  90. <i
  91. v-else-if="status === 'todo'"
  92. class="material-icons item-icon todo-icon"
  93. content="Song marked todo"
  94. v-tippy="{ theme: 'info' }"
  95. >cancel</i
  96. >
  97. </template>
  98. <template v-if="!song.removed" #actions>
  99. <i
  100. class="material-icons edit-icon"
  101. content="Edit Song"
  102. v-tippy
  103. @click="pickSong(song)"
  104. >
  105. edit
  106. </i>
  107. </template>
  108. <template #tippyActions>
  109. <i
  110. class="material-icons flag-icon"
  111. :class="{ flagged }"
  112. content="Toggle Flag"
  113. v-tippy
  114. @click="toggleFlag(index)"
  115. >
  116. flag_circle
  117. </i>
  118. </template>
  119. </song-item>
  120. </div>
  121. <p v-if="filteredItems.length === 0" class="no-items">
  122. {{
  123. flagFilter
  124. ? "No flagged songs queued"
  125. : "No songs queued"
  126. }}
  127. </p>
  128. </section>
  129. <footer class="sidebar-foot">
  130. <button
  131. @click="toggleFlagFilter()"
  132. class="button is-primary"
  133. >
  134. {{
  135. flagFilter
  136. ? "Show All Songs"
  137. : "Show Only Flagged Songs"
  138. }}
  139. </button>
  140. </footer>
  141. </div>
  142. </template>
  143. </edit-song>
  144. </template>
  145. <script>
  146. // eslint-disable-next-line no-unused-vars
  147. import { mapState, mapActions, mapGetters } from "vuex";
  148. import { defineAsyncComponent } from "vue";
  149. import SongItem from "@/components/SongItem.vue";
  150. export default {
  151. components: {
  152. EditSong: defineAsyncComponent(() =>
  153. import("@/components/modals/EditSong")
  154. ),
  155. SongItem
  156. },
  157. props: {},
  158. data() {
  159. return {
  160. items: [],
  161. currentSong: {},
  162. closed: false,
  163. flagFilter: false
  164. };
  165. },
  166. computed: {
  167. editingItemIndex() {
  168. return this.items.findIndex(
  169. item => item.song._id === this.currentSong._id
  170. );
  171. },
  172. filteredEditingItemIndex() {
  173. return this.filteredItems.findIndex(
  174. item => item.song._id === this.currentSong._id
  175. );
  176. },
  177. filteredItems: {
  178. get() {
  179. return this.items.filter(item =>
  180. this.flagFilter ? item.flagged : true
  181. );
  182. },
  183. set(newItem) {
  184. const index = this.items.findIndex(
  185. item => item.song._id === newItem._id
  186. );
  187. this.item[index] = newItem;
  188. }
  189. },
  190. ...mapState("modals/editSongs", {
  191. songIds: state => state.songIds,
  192. songPrefillData: state => state.songPrefillData
  193. }),
  194. ...mapGetters({
  195. socket: "websockets/getSocket"
  196. })
  197. },
  198. async mounted() {
  199. this.socket.dispatch("apis.joinRoom", "edit-songs");
  200. this.socket.dispatch("songs.getSongsFromSongIds", this.songIds, res => {
  201. res.data.songs.forEach(song => {
  202. this.items.push({
  203. status: "todo",
  204. flagged: false,
  205. song
  206. });
  207. });
  208. this.editNextSong();
  209. });
  210. this.socket.on(`event:admin.song.updated`, res => {
  211. const index = this.items
  212. .map(item => item.song._id)
  213. .indexOf(res.data.song._id);
  214. this.items[index].song = {
  215. ...this.items[index].song,
  216. ...res.data.song,
  217. updated: true
  218. };
  219. });
  220. this.socket.on(`event:admin.song.removed`, res => {
  221. const index = this.items
  222. .map(item => item.song._id)
  223. .indexOf(res.songId);
  224. this.items[index].song.removed = true;
  225. });
  226. },
  227. beforeUnmount() {
  228. this.socket.dispatch("apis.leaveRoom", "edit-songs");
  229. },
  230. methods: {
  231. pickSong(song) {
  232. this.editSong({
  233. songId: song._id,
  234. prefill: this.songPrefillData[song._id]
  235. });
  236. this.currentSong = song;
  237. // this.items[
  238. // this.items.findIndex(item => item.song._id === song._id)
  239. // ].status = "editing";
  240. },
  241. closeEditSongs() {
  242. this.closed = true;
  243. },
  244. editNextSong() {
  245. const currentlyEditingSongIndex = this.filteredEditingItemIndex;
  246. let newEditingSongIndex = -1;
  247. const index =
  248. currentlyEditingSongIndex + 1 === this.filteredItems.length
  249. ? 0
  250. : currentlyEditingSongIndex + 1;
  251. for (let i = index; i < this.filteredItems.length; i += 1) {
  252. if (!this.flagFilter || this.filteredItems[i].flagged) {
  253. newEditingSongIndex = i;
  254. break;
  255. }
  256. }
  257. if (newEditingSongIndex > -1)
  258. this.pickSong(this.filteredItems[newEditingSongIndex].song);
  259. },
  260. toggleFlag(songIndex = null) {
  261. const index = songIndex || this.editingItemIndex;
  262. if (index > -1)
  263. this.items[index].flagged = !this.items[index].flagged;
  264. },
  265. onSavedSuccess(songId) {
  266. const itemIndex = this.items.findIndex(
  267. item => item.song._id === songId
  268. );
  269. if (itemIndex > -1) this.items[itemIndex].status = "done";
  270. },
  271. onSavedError(songId) {
  272. const itemIndex = this.items.findIndex(
  273. item => item.song._id === songId
  274. );
  275. if (itemIndex > -1) this.items[itemIndex].status = "error";
  276. },
  277. onSaving(songId) {
  278. const itemIndex = this.items.findIndex(
  279. item => item.song._id === songId
  280. );
  281. if (itemIndex > -1) this.items[itemIndex].status = "saving";
  282. },
  283. toggleFlagFilter() {
  284. this.flagFilter = !this.flagFilter;
  285. },
  286. ...mapActions("modals/editSong", ["editSong"])
  287. }
  288. };
  289. </script>
  290. <style lang="scss" scoped>
  291. .sidebar {
  292. width: 350px;
  293. z-index: 2000;
  294. display: flex;
  295. flex-direction: column;
  296. position: relative;
  297. height: 100%;
  298. max-height: calc(100vh - 40px);
  299. overflow: auto;
  300. margin-right: 8px;
  301. // padding: 10px;
  302. border-radius: 5px;
  303. .sidebar-head,
  304. .sidebar-foot {
  305. display: flex;
  306. flex-shrink: 0;
  307. position: relative;
  308. justify-content: flex-start;
  309. align-items: center;
  310. padding: 20px;
  311. background-color: var(--light-grey);
  312. }
  313. .sidebar-head {
  314. border-bottom: 1px solid var(--light-grey-2);
  315. border-radius: 5px 5px 0 0;
  316. .sidebar-title {
  317. display: flex;
  318. flex: 1;
  319. margin: 0;
  320. font-size: 26px;
  321. font-weight: 600;
  322. }
  323. // .delete.material-icons {
  324. // font-size: 28px;
  325. // cursor: pointer;
  326. // user-select: none;
  327. // -webkit-user-drag: none;
  328. // &:hover,
  329. // &:focus {
  330. // filter: brightness(90%);
  331. // }
  332. // }
  333. }
  334. .sidebar-body {
  335. background-color: var(--white);
  336. display: flex;
  337. flex-direction: column;
  338. row-gap: 8px;
  339. flex: 1;
  340. overflow: auto;
  341. padding: 10px;
  342. .item {
  343. display: flex;
  344. flex-direction: row;
  345. align-items: center;
  346. column-gap: 8px;
  347. /deep/ .song-item {
  348. .item-icon {
  349. margin-right: 10px;
  350. cursor: pointer;
  351. }
  352. .removed-icon,
  353. .error-icon {
  354. color: var(--red);
  355. }
  356. .saving-icon,
  357. .todo-icon,
  358. .editing-icon {
  359. color: var(--primary-color);
  360. }
  361. .done-icon {
  362. color: var(--green);
  363. }
  364. .flag-icon {
  365. color: var(--orange);
  366. &.flagged {
  367. color: var(--grey);
  368. }
  369. }
  370. &.removed {
  371. filter: grayscale(100%);
  372. cursor: not-allowed;
  373. user-select: none;
  374. }
  375. }
  376. }
  377. .no-items {
  378. text-align: center;
  379. font-size: 18px;
  380. }
  381. }
  382. .sidebar-foot {
  383. border-top: 1px solid var(--light-grey-2);
  384. border-radius: 0 0 5px 5px;
  385. .button {
  386. flex: 1;
  387. }
  388. }
  389. }
  390. </style>