From 12f231b88693be2e946ee865ab75cf0771d1b690 Mon Sep 17 00:00:00 2001 From: mfcar Date: Sat, 4 Mar 2023 16:44:52 +0000 Subject: [PATCH 1/3] Add save action without closing the modal --- .../modals/podcast/tabs/EpisodeDetails.vue | 57 +++++++++++++------ 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/client/components/modals/podcast/tabs/EpisodeDetails.vue b/client/components/modals/podcast/tabs/EpisodeDetails.vue index cdc08f2f..1c486c2f 100644 --- a/client/components/modals/podcast/tabs/EpisodeDetails.vue +++ b/client/components/modals/podcast/tabs/EpisodeDetails.vue @@ -24,7 +24,12 @@
- {{ $strings.ButtonSubmit }} + + + + + + {{ $strings.ButtonSave }}

Episode URL from RSS feed

@@ -125,26 +130,44 @@ export default { } return updatePayload }, - submit() { - const payload = this.getUpdatePayload() - if (!Object.keys(payload).length) { - return this.$toast.info('No updates were made') + async saveAndClose() { + const wasUpdated = await this.submit() + if (wasUpdated !== null) this.$emit('close') + }, + async submit() { + if (this.isProcessing) { + return null } + const updatedDetails = this.getUpdatePayload() + if (!Object.keys(updatedDetails).length) { + this.$toast.info('No changes were made') + return false + } + return this.updateDetails(updatedDetails) + }, + async updateDetails(updatedDetails) { this.isProcessing = true - this.$axios - .$patch(`/api/podcasts/${this.libraryItem.id}/episode/${this.episodeId}`, payload) - .then(() => { - this.isProcessing = false + const updateResult = await this.$axios + .$patch(`/api/podcasts/${this.libraryItem.id}/episode/${this.episodeId}`, updatedDetails) + .catch((error) => { + const errorMsg = error.response && error.response.data ? error.response.data : 'Failed to update episode'; + console.error('Failed update episode', error) + this.isProcessing = false + this.$toast.error(errorMsg) + return false + }); + + this.isProcessing = false + if (updateResult) { + if (updateResult) { this.$toast.success('Podcast episode updated') - this.$emit('close') - }) - .catch((error) => { - var errorMsg = error.response && error.response.data ? error.response.data : 'Failed to update episode' - console.error('Failed update episode', error) - this.isProcessing = false - this.$toast.error(errorMsg) - }) + return true + } else { + this.$toast.info(this.$strings.MessageNoUpdatesWereNecessary) + } + } + return false } }, mounted() {} From 72396c5a98c0f0458ae3d02fecdc6a09f3e621cc Mon Sep 17 00:00:00 2001 From: mfcar Date: Sat, 4 Mar 2023 19:04:55 +0000 Subject: [PATCH 2/3] Add Prev/Next buttons on podcast editing --- .../components/modals/podcast/EditEpisode.vue | 143 +++++++++++++++++- .../tables/podcast/EpisodesTable.vue | 4 +- client/store/index.js | 10 +- server/controllers/PodcastController.js | 16 +- server/routers/ApiRouter.js | 3 +- 5 files changed, 166 insertions(+), 10 deletions(-) diff --git a/client/components/modals/podcast/EditEpisode.vue b/client/components/modals/podcast/EditEpisode.vue index 236a0d87..e2126be2 100644 --- a/client/components/modals/podcast/EditEpisode.vue +++ b/client/components/modals/podcast/EditEpisode.vue @@ -11,8 +11,15 @@
+
+
arrow_back_ios
+
+
+
arrow_forward_ios
+
+
- +
@@ -21,8 +28,8 @@ export default { data() { return { + episodeItem: null, processing: false, - selectedTab: 'details', tabs: [ { id: 'details', @@ -37,6 +44,29 @@ export default { ] } }, + watch: { + show: { + handler(newVal) { + if (newVal) { + const availableTabIds = this.tabs.map((tab) => tab.id); + if (!availableTabIds.length) { + this.show = false + return + } + + if (!availableTabIds.includes(this.selectedTab)) { + this.selectedTab = availableTabIds[0] + } + + this.episodeItem = null + this.init() + this.registerListeners() + } else { + this.unregisterListeners() + } + } + } + }, computed: { show: { get() { @@ -46,27 +76,128 @@ export default { this.$store.commit('globals/setShowEditPodcastEpisodeModal', val) } }, + selectedTab: { + get() { + return this.$store.state.editPodcastModalTab + }, + set(val) { + this.$store.commit('setEditPodcastModalTab', val) + } + }, libraryItem() { return this.$store.state.selectedLibraryItem }, episode() { return this.$store.state.globals.selectedEpisode }, + selectedEpisodeId() { + return this.episode.id + }, title() { if (!this.libraryItem) return '' return this.libraryItem.media.metadata.title || 'Unknown' }, tabComponentName() { - var _tab = this.tabs.find((t) => t.id === this.selectedTab) + const _tab = this.tabs.find((t) => t.id === this.selectedTab); return _tab ? _tab.component : '' + }, + episodeTableEpisodeIds() { + return this.$store.state.episodeTableEpisodeIds || [] + }, + currentEpisodeIndex() { + if (!this.episodeTableEpisodeIds.length) return 0 + return this.episodeTableEpisodeIds.findIndex((bid) => bid === this.selectedEpisodeId) + }, + canGoPrev() { + return this.episodeTableEpisodeIds.length && this.currentEpisodeIndex > 0 + }, + canGoNext() { + return this.episodeTableEpisodeIds.length && this.currentEpisodeIndex < this.episodeTableEpisodeIds.length - 1 } }, methods: { + async goPrevEpisode() { + if (this.currentEpisodeIndex - 1 < 0) return + const prevEpisodeId = this.episodeTableEpisodeIds[this.currentEpisodeIndex - 1]; + this.processing = true + const prevEpisode = await this.$axios.$get(`/api/podcasts/${this.libraryItem.id}/episode/${prevEpisodeId}`).catch((error) => { + const errorMsg = error.response && error.response.data ? error.response.data : 'Failed to fetch episode'; + this.$toast.error(errorMsg) + return null + }); + this.processing = false + if (prevEpisode) { + this.unregisterListeners() + this.episodeItem = prevEpisode + this.$store.commit('globals/setSelectedEpisode', prevEpisode) + this.$nextTick(this.registerListeners) + } else { + console.error('Episode not found', prevEpisodeId) + } + }, + async goNextEpisode() { + if (this.currentEpisodeIndex >= this.episodeTableEpisodeIds.length - 1) return + this.processing = true + const nextEpisodeId = this.episodeTableEpisodeIds[this.currentEpisodeIndex + 1]; + const nextEpisode = await this.$axios.$get(`/api/podcasts/${this.libraryItem.id}/episode/${nextEpisodeId}`).catch((error) => { + const errorMsg = error.response && error.response.data ? error.response.data : 'Failed to fetch book'; + this.$toast.error(errorMsg) + return null + }); + this.processing = false + if (nextEpisode) { + this.unregisterListeners() + this.episodeItem = nextEpisode + this.$store.commit('globals/setSelectedEpisode', nextEpisode) + this.$nextTick(this.registerListeners) + } else { + console.error('Episode not found', nextEpisodeId) + } + }, selectTab(tab) { - this.selectedTab = tab + if (this.selectedTab === tab) return + if (this.tabs.find((t) => t.id === tab)) { + this.selectedTab = tab + this.processing = false + } + }, + libraryItemUpdated(expandedLibraryItem) { + this.libraryItem = expandedLibraryItem + }, + init() { + this.fetchFull() + }, + async fetchFull() { + try { + this.processing = true + this.episodeItem = await this.$axios.$get(`/api/podcasts/${this.libraryItem.id}/episode/${this.selectedEpisodeId}`) + this.processing = false + } catch (error) { + console.error('Failed to fetch episode', this.selectedEpisodeId, error) + this.processing = false + this.show = false + } + }, + hotkey(action) { + if (action === this.$hotkeys.Modal.NEXT_PAGE) { + this.goNextEpisode() + } else if (action === this.$hotkeys.Modal.PREV_PAGE) { + this.goPrevEpisode() + } + }, + registerListeners() { + this.$eventBus.$on('modal-hotkey', this.hotkey) + this.$eventBus.$on(`${this.selectedLibraryItemId}_updated`, this.libraryItemUpdated) + }, + unregisterListeners() { + this.$eventBus.$off('modal-hotkey', this.hotkey) + this.$eventBus.$off(`${this.selectedLibraryItemId}_updated`, this.libraryItemUpdated) } }, - mounted() {} + mounted() {}, + beforeDestroy() { + this.unregisterListeners() + } } @@ -77,4 +208,4 @@ export default { .tab.tab-selected { height: 41px; } - \ No newline at end of file + diff --git a/client/components/tables/podcast/EpisodesTable.vue b/client/components/tables/podcast/EpisodesTable.vue index 39228a39..03883bc1 100644 --- a/client/components/tables/podcast/EpisodesTable.vue +++ b/client/components/tables/podcast/EpisodesTable.vue @@ -281,6 +281,8 @@ export default { this.showPodcastRemoveModal = true }, editEpisode(episode) { + const episodeIds = this.episodesSorted.map((e) => e.id) + this.$store.commit('setEpisodeTableEpisodeIds', episodeIds) this.$store.commit('setSelectedLibraryItem', this.libraryItem) this.$store.commit('globals/setSelectedEpisode', episode) this.$store.commit('globals/setShowEditPodcastEpisodeModal', true) @@ -314,4 +316,4 @@ export default { .episode-leave-active { position: absolute; } - \ No newline at end of file + diff --git a/client/store/index.js b/client/store/index.js index 1529d864..57710da2 100644 --- a/client/store/index.js +++ b/client/store/index.js @@ -13,6 +13,7 @@ export const state = () => ({ playerQueueAutoPlay: true, playerIsFullscreen: false, editModalTab: 'details', + editPodcastModalTab: 'details', showEditModal: false, showEReader: false, selectedLibraryItem: null, @@ -21,6 +22,7 @@ export const state = () => ({ previousPath: '/', showExperimentalFeatures: false, bookshelfBookIds: [], + episodeTableEpisodeIds: [], openModal: null, innerModalOpen: false, lastBookshelfScrollData: {}, @@ -135,6 +137,9 @@ export const mutations = { setBookshelfBookIds(state, val) { state.bookshelfBookIds = val || [] }, + setEpisodeTableEpisodeIds(state, val) { + state.episodeTableEpisodeIds = val || [] + }, setPreviousPath(state, val) { state.previousPath = val }, @@ -198,6 +203,9 @@ export const mutations = { setShowEditModal(state, val) { state.showEditModal = val }, + setEditPodcastModalTab(state, tab) { + state.editPodcastModalTab = tab + }, showEReader(state, libraryItem) { state.selectedLibraryItem = libraryItem @@ -225,4 +233,4 @@ export const mutations = { setInnerModalOpen(state, val) { state.innerModalOpen = val } -} \ No newline at end of file +} diff --git a/server/controllers/PodcastController.js b/server/controllers/PodcastController.js index 23cec882..60f1e35d 100644 --- a/server/controllers/PodcastController.js +++ b/server/controllers/PodcastController.js @@ -225,6 +225,20 @@ class PodcastController { res.json(libraryItem.toJSONExpanded()) } + // GET: api/podcasts/:id/episode/:episodeId + async getEpisode(req, res) { + const episodeId = req.params.episodeId; + const libraryItem = req.libraryItem; + + const episode = libraryItem.media.episodes.find(ep => ep.id === episodeId); + if (!episode) { + Logger.error(`[PodcastController] getEpisode episode ${episodeId} not found for item ${libraryItem.id}`) + return res.sendStatus(404) + } + + res.json(episode) + } + // DELETE: api/podcasts/:id/episode/:episodeId async removeEpisode(req, res) { var episodeId = req.params.episodeId @@ -283,4 +297,4 @@ class PodcastController { next() } } -module.exports = new PodcastController() \ No newline at end of file +module.exports = new PodcastController() diff --git a/server/routers/ApiRouter.js b/server/routers/ApiRouter.js index 41c26769..53721706 100644 --- a/server/routers/ApiRouter.js +++ b/server/routers/ApiRouter.js @@ -235,6 +235,7 @@ class ApiRouter { this.router.get('/podcasts/:id/search-episode', PodcastController.middleware.bind(this), PodcastController.findEpisode.bind(this)) this.router.post('/podcasts/:id/download-episodes', PodcastController.middleware.bind(this), PodcastController.downloadEpisodes.bind(this)) this.router.post('/podcasts/:id/match-episodes', PodcastController.middleware.bind(this), PodcastController.quickMatchEpisodes.bind(this)) + this.router.get('/podcasts/:id/episode/:episodeId', PodcastController.middleware.bind(this), PodcastController.getEpisode.bind(this)) this.router.patch('/podcasts/:id/episode/:episodeId', PodcastController.middleware.bind(this), PodcastController.updateEpisode.bind(this)) this.router.delete('/podcasts/:id/episode/:episodeId', PodcastController.middleware.bind(this), PodcastController.removeEpisode.bind(this)) @@ -553,4 +554,4 @@ class ApiRouter { } } } -module.exports = ApiRouter \ No newline at end of file +module.exports = ApiRouter From dccad3055b954dfa5c66611a3142fe42e47706e1 Mon Sep 17 00:00:00 2001 From: advplyr Date: Sun, 5 Mar 2023 12:28:20 -0600 Subject: [PATCH 3/3] Remove library item listener from edit episode modal --- .../components/modals/podcast/EditEpisode.vue | 28 ++++++------------- .../modals/podcast/tabs/EpisodeDetails.vue | 15 ++++------ client/components/ui/RichTextEditor.vue | 4 +-- server/controllers/PodcastController.js | 10 +++---- 4 files changed, 21 insertions(+), 36 deletions(-) diff --git a/client/components/modals/podcast/EditEpisode.vue b/client/components/modals/podcast/EditEpisode.vue index e2126be2..ed9c8f28 100644 --- a/client/components/modals/podcast/EditEpisode.vue +++ b/client/components/modals/podcast/EditEpisode.vue @@ -48,7 +48,7 @@ export default { show: { handler(newVal) { if (newVal) { - const availableTabIds = this.tabs.map((tab) => tab.id); + const availableTabIds = this.tabs.map((tab) => tab.id) if (!availableTabIds.length) { this.show = false return @@ -94,11 +94,10 @@ export default { return this.episode.id }, title() { - if (!this.libraryItem) return '' - return this.libraryItem.media.metadata.title || 'Unknown' + return this.libraryItem?.media.metadata.title || 'Unknown' }, tabComponentName() { - const _tab = this.tabs.find((t) => t.id === this.selectedTab); + const _tab = this.tabs.find((t) => t.id === this.selectedTab) return _tab ? _tab.component : '' }, episodeTableEpisodeIds() { @@ -118,19 +117,17 @@ export default { methods: { async goPrevEpisode() { if (this.currentEpisodeIndex - 1 < 0) return - const prevEpisodeId = this.episodeTableEpisodeIds[this.currentEpisodeIndex - 1]; + const prevEpisodeId = this.episodeTableEpisodeIds[this.currentEpisodeIndex - 1] this.processing = true const prevEpisode = await this.$axios.$get(`/api/podcasts/${this.libraryItem.id}/episode/${prevEpisodeId}`).catch((error) => { - const errorMsg = error.response && error.response.data ? error.response.data : 'Failed to fetch episode'; + const errorMsg = error.response && error.response.data ? error.response.data : 'Failed to fetch episode' this.$toast.error(errorMsg) return null - }); + }) this.processing = false if (prevEpisode) { - this.unregisterListeners() this.episodeItem = prevEpisode this.$store.commit('globals/setSelectedEpisode', prevEpisode) - this.$nextTick(this.registerListeners) } else { console.error('Episode not found', prevEpisodeId) } @@ -138,18 +135,16 @@ export default { async goNextEpisode() { if (this.currentEpisodeIndex >= this.episodeTableEpisodeIds.length - 1) return this.processing = true - const nextEpisodeId = this.episodeTableEpisodeIds[this.currentEpisodeIndex + 1]; + const nextEpisodeId = this.episodeTableEpisodeIds[this.currentEpisodeIndex + 1] const nextEpisode = await this.$axios.$get(`/api/podcasts/${this.libraryItem.id}/episode/${nextEpisodeId}`).catch((error) => { - const errorMsg = error.response && error.response.data ? error.response.data : 'Failed to fetch book'; + const errorMsg = error.response && error.response.data ? error.response.data : 'Failed to fetch book' this.$toast.error(errorMsg) return null - }); + }) this.processing = false if (nextEpisode) { - this.unregisterListeners() this.episodeItem = nextEpisode this.$store.commit('globals/setSelectedEpisode', nextEpisode) - this.$nextTick(this.registerListeners) } else { console.error('Episode not found', nextEpisodeId) } @@ -161,9 +156,6 @@ export default { this.processing = false } }, - libraryItemUpdated(expandedLibraryItem) { - this.libraryItem = expandedLibraryItem - }, init() { this.fetchFull() }, @@ -187,11 +179,9 @@ export default { }, registerListeners() { this.$eventBus.$on('modal-hotkey', this.hotkey) - this.$eventBus.$on(`${this.selectedLibraryItemId}_updated`, this.libraryItemUpdated) }, unregisterListeners() { this.$eventBus.$off('modal-hotkey', this.hotkey) - this.$eventBus.$off(`${this.selectedLibraryItemId}_updated`, this.libraryItemUpdated) } }, mounted() {}, diff --git a/client/components/modals/podcast/tabs/EpisodeDetails.vue b/client/components/modals/podcast/tabs/EpisodeDetails.vue index 1c486c2f..debf9155 100644 --- a/client/components/modals/podcast/tabs/EpisodeDetails.vue +++ b/client/components/modals/podcast/tabs/EpisodeDetails.vue @@ -148,15 +148,12 @@ export default { }, async updateDetails(updatedDetails) { this.isProcessing = true - const updateResult = await this.$axios - .$patch(`/api/podcasts/${this.libraryItem.id}/episode/${this.episodeId}`, updatedDetails) - .catch((error) => { - const errorMsg = error.response && error.response.data ? error.response.data : 'Failed to update episode'; - console.error('Failed update episode', error) - this.isProcessing = false - this.$toast.error(errorMsg) - return false - }); + const updateResult = await this.$axios.$patch(`/api/podcasts/${this.libraryItem.id}/episode/${this.episodeId}`, updatedDetails).catch((error) => { + console.error('Failed update episode', error) + this.isProcessing = false + this.$toast.error(error?.response?.data || 'Failed to update episode') + return false + }) this.isProcessing = false if (updateResult) { diff --git a/client/components/ui/RichTextEditor.vue b/client/components/ui/RichTextEditor.vue index 068bc95f..582f5e8f 100644 --- a/client/components/ui/RichTextEditor.vue +++ b/client/components/ui/RichTextEditor.vue @@ -68,8 +68,6 @@ export default { } }, mounted() {}, - beforeDestroy() { - console.log('Before destroy') - } + beforeDestroy() {} } \ No newline at end of file diff --git a/server/controllers/PodcastController.js b/server/controllers/PodcastController.js index 60f1e35d..f19bba9f 100644 --- a/server/controllers/PodcastController.js +++ b/server/controllers/PodcastController.js @@ -227,13 +227,13 @@ class PodcastController { // GET: api/podcasts/:id/episode/:episodeId async getEpisode(req, res) { - const episodeId = req.params.episodeId; - const libraryItem = req.libraryItem; + const episodeId = req.params.episodeId + const libraryItem = req.libraryItem - const episode = libraryItem.media.episodes.find(ep => ep.id === episodeId); + const episode = libraryItem.media.episodes.find(ep => ep.id === episodeId) if (!episode) { - Logger.error(`[PodcastController] getEpisode episode ${episodeId} not found for item ${libraryItem.id}`) - return res.sendStatus(404) + Logger.error(`[PodcastController] getEpisode episode ${episodeId} not found for item ${libraryItem.id}`) + return res.sendStatus(404) } res.json(episode)