mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-01-03 00:06:46 +01:00
Merge pull request #1581 from mfcar/improvePodcastEditing
Improve podcast editing
This commit is contained in:
commit
aef2c52630
@ -11,8 +11,15 @@
|
|||||||
</template>
|
</template>
|
||||||
</div>
|
</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">
|
<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>
|
</div>
|
||||||
</modals-modal>
|
</modals-modal>
|
||||||
</template>
|
</template>
|
||||||
@ -21,8 +28,8 @@
|
|||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
episodeItem: null,
|
||||||
processing: false,
|
processing: false,
|
||||||
selectedTab: 'details',
|
|
||||||
tabs: [
|
tabs: [
|
||||||
{
|
{
|
||||||
id: 'details',
|
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: {
|
computed: {
|
||||||
show: {
|
show: {
|
||||||
get() {
|
get() {
|
||||||
@ -46,27 +76,118 @@ export default {
|
|||||||
this.$store.commit('globals/setShowEditPodcastEpisodeModal', val)
|
this.$store.commit('globals/setShowEditPodcastEpisodeModal', val)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
selectedTab: {
|
||||||
|
get() {
|
||||||
|
return this.$store.state.editPodcastModalTab
|
||||||
|
},
|
||||||
|
set(val) {
|
||||||
|
this.$store.commit('setEditPodcastModalTab', val)
|
||||||
|
}
|
||||||
|
},
|
||||||
libraryItem() {
|
libraryItem() {
|
||||||
return this.$store.state.selectedLibraryItem
|
return this.$store.state.selectedLibraryItem
|
||||||
},
|
},
|
||||||
episode() {
|
episode() {
|
||||||
return this.$store.state.globals.selectedEpisode
|
return this.$store.state.globals.selectedEpisode
|
||||||
},
|
},
|
||||||
|
selectedEpisodeId() {
|
||||||
|
return this.episode.id
|
||||||
|
},
|
||||||
title() {
|
title() {
|
||||||
if (!this.libraryItem) return ''
|
return this.libraryItem?.media.metadata.title || 'Unknown'
|
||||||
return this.libraryItem.media.metadata.title || 'Unknown'
|
|
||||||
},
|
},
|
||||||
tabComponentName() {
|
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 : ''
|
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: {
|
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) {
|
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>
|
</script>
|
||||||
|
|
||||||
|
@ -24,7 +24,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-end pt-4">
|
<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>
|
||||||
<div v-if="enclosureUrl" class="py-4">
|
<div v-if="enclosureUrl" class="py-4">
|
||||||
<p class="text-xs text-gray-300 font-semibold">Episode URL from RSS feed</p>
|
<p class="text-xs text-gray-300 font-semibold">Episode URL from RSS feed</p>
|
||||||
@ -125,26 +130,41 @@ export default {
|
|||||||
}
|
}
|
||||||
return updatePayload
|
return updatePayload
|
||||||
},
|
},
|
||||||
submit() {
|
async saveAndClose() {
|
||||||
const payload = this.getUpdatePayload()
|
const wasUpdated = await this.submit()
|
||||||
if (!Object.keys(payload).length) {
|
if (wasUpdated !== null) this.$emit('close')
|
||||||
return this.$toast.info('No updates were made')
|
},
|
||||||
|
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.isProcessing = true
|
||||||
this.$axios
|
const updateResult = await this.$axios.$patch(`/api/podcasts/${this.libraryItem.id}/episode/${this.episodeId}`, updatedDetails).catch((error) => {
|
||||||
.$patch(`/api/podcasts/${this.libraryItem.id}/episode/${this.episodeId}`, payload)
|
console.error('Failed update episode', error)
|
||||||
.then(() => {
|
this.isProcessing = false
|
||||||
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.$toast.success('Podcast episode updated')
|
||||||
this.$emit('close')
|
return true
|
||||||
})
|
} else {
|
||||||
.catch((error) => {
|
this.$toast.info(this.$strings.MessageNoUpdatesWereNecessary)
|
||||||
var errorMsg = error.response && error.response.data ? error.response.data : 'Failed to update episode'
|
}
|
||||||
console.error('Failed update episode', error)
|
}
|
||||||
this.isProcessing = false
|
return false
|
||||||
this.$toast.error(errorMsg)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {}
|
mounted() {}
|
||||||
|
@ -287,6 +287,8 @@ export default {
|
|||||||
this.showPodcastRemoveModal = true
|
this.showPodcastRemoveModal = true
|
||||||
},
|
},
|
||||||
editEpisode(episode) {
|
editEpisode(episode) {
|
||||||
|
const episodeIds = this.episodesSorted.map((e) => e.id)
|
||||||
|
this.$store.commit('setEpisodeTableEpisodeIds', episodeIds)
|
||||||
this.$store.commit('setSelectedLibraryItem', this.libraryItem)
|
this.$store.commit('setSelectedLibraryItem', this.libraryItem)
|
||||||
this.$store.commit('globals/setSelectedEpisode', episode)
|
this.$store.commit('globals/setSelectedEpisode', episode)
|
||||||
this.$store.commit('globals/setShowEditPodcastEpisodeModal', true)
|
this.$store.commit('globals/setShowEditPodcastEpisodeModal', true)
|
||||||
|
@ -68,8 +68,6 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {},
|
mounted() {},
|
||||||
beforeDestroy() {
|
beforeDestroy() {}
|
||||||
console.log('Before destroy')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
@ -13,6 +13,7 @@ export const state = () => ({
|
|||||||
playerQueueAutoPlay: true,
|
playerQueueAutoPlay: true,
|
||||||
playerIsFullscreen: false,
|
playerIsFullscreen: false,
|
||||||
editModalTab: 'details',
|
editModalTab: 'details',
|
||||||
|
editPodcastModalTab: 'details',
|
||||||
showEditModal: false,
|
showEditModal: false,
|
||||||
showEReader: false,
|
showEReader: false,
|
||||||
selectedLibraryItem: null,
|
selectedLibraryItem: null,
|
||||||
@ -21,6 +22,7 @@ export const state = () => ({
|
|||||||
previousPath: '/',
|
previousPath: '/',
|
||||||
showExperimentalFeatures: false,
|
showExperimentalFeatures: false,
|
||||||
bookshelfBookIds: [],
|
bookshelfBookIds: [],
|
||||||
|
episodeTableEpisodeIds: [],
|
||||||
openModal: null,
|
openModal: null,
|
||||||
innerModalOpen: false,
|
innerModalOpen: false,
|
||||||
lastBookshelfScrollData: {},
|
lastBookshelfScrollData: {},
|
||||||
@ -135,6 +137,9 @@ export const mutations = {
|
|||||||
setBookshelfBookIds(state, val) {
|
setBookshelfBookIds(state, val) {
|
||||||
state.bookshelfBookIds = val || []
|
state.bookshelfBookIds = val || []
|
||||||
},
|
},
|
||||||
|
setEpisodeTableEpisodeIds(state, val) {
|
||||||
|
state.episodeTableEpisodeIds = val || []
|
||||||
|
},
|
||||||
setPreviousPath(state, val) {
|
setPreviousPath(state, val) {
|
||||||
state.previousPath = val
|
state.previousPath = val
|
||||||
},
|
},
|
||||||
@ -198,6 +203,9 @@ export const mutations = {
|
|||||||
setShowEditModal(state, val) {
|
setShowEditModal(state, val) {
|
||||||
state.showEditModal = val
|
state.showEditModal = val
|
||||||
},
|
},
|
||||||
|
setEditPodcastModalTab(state, tab) {
|
||||||
|
state.editPodcastModalTab = tab
|
||||||
|
},
|
||||||
showEReader(state, libraryItem) {
|
showEReader(state, libraryItem) {
|
||||||
state.selectedLibraryItem = libraryItem
|
state.selectedLibraryItem = libraryItem
|
||||||
|
|
||||||
|
@ -225,6 +225,20 @@ class PodcastController {
|
|||||||
res.json(libraryItem.toJSONExpanded())
|
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
|
// DELETE: api/podcasts/:id/episode/:episodeId
|
||||||
async removeEpisode(req, res) {
|
async removeEpisode(req, res) {
|
||||||
var episodeId = req.params.episodeId
|
var episodeId = req.params.episodeId
|
||||||
|
@ -236,6 +236,7 @@ class ApiRouter {
|
|||||||
this.router.get('/podcasts/:id/search-episode', PodcastController.middleware.bind(this), PodcastController.findEpisode.bind(this))
|
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/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.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.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))
|
this.router.delete('/podcasts/:id/episode/:episodeId', PodcastController.middleware.bind(this), PodcastController.removeEpisode.bind(this))
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user