mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2025-08-28 05:53:59 +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="w-full">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<p class="text-lg mb-1 font-semibold">Episodes ({{ episodesFiltered.length }})</p>
|
<p class="text-lg mb-1 font-semibold">Episodes ({{ episodesFiltered.length }})</p>
|
||||||
|
|
||||||
<div class="flex-grow" />
|
<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">
|
<button class="outline:none mx-3 pt-0.5 relative" @click="showFilters">
|
||||||
<span class="material-icons text-xl text-gray-200">filter_alt</span>
|
<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" />
|
<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" />
|
<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>
|
</template>
|
||||||
|
|
||||||
<!-- What in tarnation is going on here?
|
<!-- Huhhh?
|
||||||
Without anything below the template it will not re-render -->
|
Without anything below the template it will not re-render -->
|
||||||
<p> </p>
|
<p> </p>
|
||||||
|
|
||||||
<modals-dialog v-model="showFiltersModal" title="Episode Filter" :items="filterItems" :selected="filterKey" @action="setFilter" />
|
<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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -86,7 +95,10 @@ export default {
|
||||||
text: 'Complete',
|
text: 'Complete',
|
||||||
value: 'complete'
|
value: 'complete'
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
fetchingRSSFeed: false,
|
||||||
|
podcastFeedEpisodes: [],
|
||||||
|
showPodcastEpisodeFeed: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
@ -98,9 +110,18 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
isAdminOrUp() {
|
||||||
|
return this.$store.getters['user/getIsAdminOrUp']
|
||||||
|
},
|
||||||
libraryItemId() {
|
libraryItemId() {
|
||||||
return this.libraryItem ? this.libraryItem.id : null
|
return this.libraryItem ? this.libraryItem.id : null
|
||||||
},
|
},
|
||||||
|
media() {
|
||||||
|
return this.libraryItem ? this.libraryItem.media || {} : {}
|
||||||
|
},
|
||||||
|
mediaMetadata() {
|
||||||
|
return this.media.metadata || {}
|
||||||
|
},
|
||||||
episodesAreFiltered() {
|
episodesAreFiltered() {
|
||||||
return this.episodesFiltered.length !== this.episodesCopy.length
|
return this.episodesFiltered.length !== this.episodesCopy.length
|
||||||
},
|
},
|
||||||
|
@ -146,6 +167,29 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
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) {
|
addEpisodeToPlaylist(episode) {
|
||||||
this.$store.commit('globals/setSelectedPlaylistItems', [{ libraryItem: this.libraryItem, episode }])
|
this.$store.commit('globals/setSelectedPlaylistItems', [{ libraryItem: this.libraryItem, episode }])
|
||||||
this.$store.commit('globals/setShowPlaylistsAddCreateModal', true)
|
this.$store.commit('globals/setShowPlaylistsAddCreateModal', true)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue