mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2025-08-01 08:34:33 +02:00
Add:Feed podcast episodes select modal #225
This commit is contained in:
parent
99217fee48
commit
64325fe2a6
2 changed files with 138 additions and 2 deletions
92
components/modals/PodcastEpisodesFeedModal.vue
Normal file
92
components/modals/PodcastEpisodesFeedModal.vue
Normal file
|
@ -0,0 +1,92 @@
|
|||
<template>
|
||||
<modals-modal v-model="show" width="100%" height="100%" max-width="100%">
|
||||
<template #outer>
|
||||
<div class="absolute top-6 left-4 z-40">
|
||||
<p class="text-white text-2xl truncate">Feed Episodes</p>
|
||||
</div>
|
||||
</template>
|
||||
<div class="w-full h-full overflow-hidden absolute top-0 left-0 flex items-center justify-center" @click="show = false">
|
||||
<div class="feed-content w-full overflow-x-hidden overflow-y-auto bg-bg rounded-lg border border-white border-opacity-20">
|
||||
<template v-for="(episode, index) in episodes">
|
||||
<div :key="index" class="relative" :class="index % 2 == 0 ? 'bg-primary bg-opacity-50' : 'bg-primary bg-opacity-25'">
|
||||
<div class="absolute top-0 left-0 h-full flex items-center p-2">
|
||||
<span v-if="itemEpisodeMap[episode.enclosure.url]" class="material-icons text-success text-xl">download_done</span>
|
||||
<!-- <ui-checkbox v-else v-model="selectedEpisodes[String(index)]" small checkbox-bg="primary" border-color="gray-600" /> -->
|
||||
</div>
|
||||
<div class="px-2 py-2 border-b border-white border-opacity-10">
|
||||
<p v-if="episode.episode" class="font-semibold text-gray-200 text-xs">#{{ episode.episode }}</p>
|
||||
<p class="break-words mb-1 text-sm">{{ episode.title }}</p>
|
||||
<p v-if="episode.subtitle" class="break-words mb-1 text-xs text-gray-300 episode-subtitle">{{ episode.subtitle }}</p>
|
||||
<p class="text-xxs text-gray-300">Published {{ episode.publishedAt ? $dateDistanceFromNow(episode.publishedAt) : 'Unknown' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="absolute bottom-0 left-0 w-full flex items-center" style="height: 50px">
|
||||
<ui-btn class="w-full" color="success">Download Episodes</ui-btn>
|
||||
</div>
|
||||
</div>
|
||||
</modals-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
value: Boolean,
|
||||
libraryItem: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
episodes: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
watch: {
|
||||
show: {
|
||||
immediate: true,
|
||||
handler(newVal) {
|
||||
if (newVal) this.init()
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
show: {
|
||||
get() {
|
||||
return this.value
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('input', val)
|
||||
}
|
||||
},
|
||||
itemEpisodes() {
|
||||
if (!this.libraryItem) return []
|
||||
return this.libraryItem.media.episodes || []
|
||||
},
|
||||
itemEpisodeMap() {
|
||||
var map = {}
|
||||
this.itemEpisodes.forEach((item) => {
|
||||
if (item.enclosure) map[item.enclosure.url] = true
|
||||
})
|
||||
return map
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
this.episodes.sort((a, b) => (a.publishedAt < b.publishedAt ? 1 : -1))
|
||||
}
|
||||
},
|
||||
mounted() {}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.feed-content {
|
||||
height: calc(100vh - 125px);
|
||||
max-height: calc(100vh - 125px);
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
|
@ -2,7 +2,14 @@
|
|||
<div class="w-full">
|
||||
<div class="flex items-center">
|
||||
<p class="text-lg mb-1 font-semibold">Episodes ({{ episodesFiltered.length }})</p>
|
||||
|
||||
<div class="flex-grow" />
|
||||
|
||||
<button v-if="isAdminOrUp && !fetchingRSSFeed" class="outline:none mx-1 pt-0.5 relative" @click="searchEpisodes">
|
||||
<span class="material-icons text-xl text-gray-200">search</span>
|
||||
</button>
|
||||
<widgets-loading-spinner v-else-if="fetchingRSSFeed" class="mx-1" />
|
||||
|
||||
<button class="outline:none mx-3 pt-0.5 relative" @click="showFilters">
|
||||
<span class="material-icons text-xl text-gray-200">filter_alt</span>
|
||||
<div v-show="filterKey !== 'all' && episodesAreFiltered" class="absolute top-0 right-0 w-1.5 h-1.5 rounded-full bg-success border border-green-300 shadow-sm z-10 pointer-events-none" />
|
||||
|
@ -18,11 +25,13 @@
|
|||
<tables-podcast-episode-row :episode="episode" :local-episode="localEpisodeMap[episode.id]" :library-item-id="libraryItemId" :local-library-item-id="localLibraryItemId" :is-local="isLocal" :key="episode.id" @addToPlaylist="addEpisodeToPlaylist" />
|
||||
</template>
|
||||
|
||||
<!-- What in tarnation is going on here?
|
||||
<!-- Huhhh?
|
||||
Without anything below the template it will not re-render -->
|
||||
<p> </p>
|
||||
|
||||
<modals-dialog v-model="showFiltersModal" title="Episode Filter" :items="filterItems" :selected="filterKey" @action="setFilter" />
|
||||
|
||||
<modals-podcast-episodes-feed-modal v-model="showPodcastEpisodeFeed" :library-item="libraryItem" :episodes="podcastFeedEpisodes" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -86,7 +95,10 @@ export default {
|
|||
text: 'Complete',
|
||||
value: 'complete'
|
||||
}
|
||||
]
|
||||
],
|
||||
fetchingRSSFeed: false,
|
||||
podcastFeedEpisodes: [],
|
||||
showPodcastEpisodeFeed: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
@ -98,9 +110,18 @@ export default {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
isAdminOrUp() {
|
||||
return this.$store.getters['user/getIsAdminOrUp']
|
||||
},
|
||||
libraryItemId() {
|
||||
return this.libraryItem ? this.libraryItem.id : null
|
||||
},
|
||||
media() {
|
||||
return this.libraryItem ? this.libraryItem.media || {} : {}
|
||||
},
|
||||
mediaMetadata() {
|
||||
return this.media.metadata || {}
|
||||
},
|
||||
episodesAreFiltered() {
|
||||
return this.episodesFiltered.length !== this.episodesCopy.length
|
||||
},
|
||||
|
@ -146,6 +167,29 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
async searchEpisodes() {
|
||||
if (!this.mediaMetadata.feedUrl) {
|
||||
return this.$toast.error('Podcast does not have an RSS Feed')
|
||||
}
|
||||
this.fetchingRSSFeed = true
|
||||
const payload = await this.$axios.$post(`/api/podcasts/feed`, { rssFeed: this.mediaMetadata.feedUrl }).catch((error) => {
|
||||
console.error('Failed to get feed', error)
|
||||
this.$toast.error('Failed to get podcast feed')
|
||||
return null
|
||||
})
|
||||
this.fetchingRSSFeed = false
|
||||
if (!payload) return
|
||||
|
||||
console.log('Podcast feed', payload)
|
||||
const podcastfeed = payload.podcast
|
||||
if (!podcastfeed.episodes || !podcastfeed.episodes.length) {
|
||||
this.$toast.info('No episodes found in RSS feed')
|
||||
return
|
||||
}
|
||||
|
||||
this.podcastFeedEpisodes = podcastfeed.episodes
|
||||
this.showPodcastEpisodeFeed = true
|
||||
},
|
||||
addEpisodeToPlaylist(episode) {
|
||||
this.$store.commit('globals/setSelectedPlaylistItems', [{ libraryItem: this.libraryItem, episode }])
|
||||
this.$store.commit('globals/setShowPlaylistsAddCreateModal', true)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue