mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Add Prev/Next buttons on podcast editing
This commit is contained in:
		
							parent
							
								
									12f231b886
								
							
						
					
					
						commit
						72396c5a98
					
				@ -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,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()
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
@ -77,4 +208,4 @@ export default {
 | 
			
		||||
.tab.tab-selected {
 | 
			
		||||
  height: 41px;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
@ -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()
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
module.exports = ApiRouter
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user