mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2024-12-20 19:06:06 +01:00
Merge pull request #1581 from mfcar/improvePodcastEditing
Improve podcast editing
This commit is contained in:
commit
aef2c52630
@ -11,8 +11,15 @@
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div v-show="canGoPrev" class="absolute -left-24 top-0 bottom-0 h-full pointer-events-none flex items-center px-6">
|
||||
<div class="material-icons text-5xl text-white text-opacity-50 hover:text-opacity-90 cursor-pointer pointer-events-auto" @click.stop.prevent="goPrevEpisode" @mousedown.prevent>arrow_back_ios</div>
|
||||
</div>
|
||||
<div v-show="canGoNext" class="absolute -right-24 top-0 bottom-0 h-full pointer-events-none flex items-center px-6">
|
||||
<div class="material-icons text-5xl text-white text-opacity-50 hover:text-opacity-90 cursor-pointer pointer-events-auto" @click.stop.prevent="goNextEpisode" @mousedown.prevent>arrow_forward_ios</div>
|
||||
</div>
|
||||
|
||||
<div ref="wrapper" class="p-4 w-full text-sm rounded-b-lg rounded-tr-lg bg-bg shadow-lg border border-black-300 relative overflow-y-auto" style="max-height: 80vh">
|
||||
<component v-if="libraryItem && show" :is="tabComponentName" :library-item="libraryItem" :episode="episode" :processing.sync="processing" @close="show = false" @selectTab="selectTab" />
|
||||
<component v-if="libraryItem && show" :is="tabComponentName" :library-item="libraryItem" :episode="episodeItem" :processing.sync="processing" @close="show = false" @selectTab="selectTab" />
|
||||
</div>
|
||||
</modals-modal>
|
||||
</template>
|
||||
@ -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,118 @@ 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'
|
||||
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.episodeItem = prevEpisode
|
||||
this.$store.commit('globals/setSelectedEpisode', prevEpisode)
|
||||
} 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.episodeItem = nextEpisode
|
||||
this.$store.commit('globals/setSelectedEpisode', nextEpisode)
|
||||
} 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
|
||||
}
|
||||
},
|
||||
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)
|
||||
},
|
||||
unregisterListeners() {
|
||||
this.$eventBus.$off('modal-hotkey', this.hotkey)
|
||||
}
|
||||
},
|
||||
mounted() {}
|
||||
mounted() {},
|
||||
beforeDestroy() {
|
||||
this.unregisterListeners()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -77,4 +198,4 @@ export default {
|
||||
.tab.tab-selected {
|
||||
height: 41px;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
@ -24,7 +24,12 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-end pt-4">
|
||||
<ui-btn @click="submit">{{ $strings.ButtonSubmit }}</ui-btn>
|
||||
<!-- desktop -->
|
||||
<ui-btn @click="submit" class="mx-2 hidden md:block">{{ $strings.ButtonSave }}</ui-btn>
|
||||
<ui-btn @click="saveAndClose" class="mx-2 hidden md:block">{{ $strings.ButtonSaveAndClose }}</ui-btn>
|
||||
|
||||
<!-- mobile -->
|
||||
<ui-btn @click="saveAndClose" class="mx-2 md:hidden">{{ $strings.ButtonSave }}</ui-btn>
|
||||
</div>
|
||||
<div v-if="enclosureUrl" class="py-4">
|
||||
<p class="text-xs text-gray-300 font-semibold">Episode URL from RSS feed</p>
|
||||
@ -125,26 +130,41 @@ 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) => {
|
||||
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) {
|
||||
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() {}
|
||||
|
@ -287,6 +287,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)
|
||||
|
@ -68,8 +68,6 @@ export default {
|
||||
}
|
||||
},
|
||||
mounted() {},
|
||||
beforeDestroy() {
|
||||
console.log('Before destroy')
|
||||
}
|
||||
beforeDestroy() {}
|
||||
}
|
||||
</script>
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
module.exports = new PodcastController()
|
||||
|
@ -236,6 +236,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))
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user