repod/src/components/Feed/Episodes.vue

276 lines
6.9 KiB
Vue
Raw Normal View History

2023-08-24 00:42:01 +02:00
<template>
2023-12-24 10:18:21 +01:00
<div>
2023-12-23 22:49:23 +01:00
<Loading v-if="loading" />
<ul v-if="!loading">
<NcListItem
v-for="episode in filteredEpisodes"
:key="episode.guid"
2023-08-30 09:27:12 +02:00
:active="isCurrentEpisode(episode)"
:class="hasEnded(episode) ? 'ended' : ''"
:details="formatLocaleDate(new Date(episode.pubDate?.date))"
2023-08-24 20:53:54 +02:00
:force-display-actions="true"
2024-08-10 22:05:06 +02:00
:href="$route.href"
:name="episode.name"
2024-08-10 22:05:06 +02:00
target="_self"
:title="episode.description"
2023-12-24 16:34:27 +01:00
@click="modalEpisode = episode">
<template #extra-actions>
<NcActionButton
v-if="!isCurrentEpisode(episode)"
:aria-label="t('repod', 'Play')"
:title="t('repod', 'Play')"
@click="load(episode, url)">
2023-08-24 20:53:54 +02:00
<template #icon>
2024-03-16 18:35:31 +01:00
<PlayIcon :size="20" />
2023-08-24 20:53:54 +02:00
</template>
</NcActionButton>
<NcActionButton
v-if="isCurrentEpisode(episode)"
:aria-label="t('repod', 'Stop')"
:title="t('repod', 'Stop')"
@click="load(null)">
2023-08-29 11:43:17 +02:00
<template #icon>
2024-03-16 18:35:31 +01:00
<StopIcon :size="20" />
2023-08-29 11:43:17 +02:00
</template>
</NcActionButton>
2023-08-24 20:53:54 +02:00
</template>
<template #actions>
<NcActionButton
v-if="episode.duration && !hasEnded(episode)"
:aria-label="t('repod', 'Mark as read')"
:disabled="loadingAction"
:name="t('repod', 'Mark as read')"
:title="t('repod', 'Mark as read')"
@click="markAs(episode, true)">
<template #icon>
<PlaylistPlayIcon :size="20" />
</template>
</NcActionButton>
<NcActionButton
v-if="episode.duration && hasEnded(episode)"
:aria-label="t('repod', 'Mark as unread')"
:disabled="loadingAction"
:name="t('repod', 'Mark as unread')"
:title="t('repod', 'Mark as unread')"
@click="markAs(episode, false)">
<template #icon>
<PlaylistRemoveIcon :size="20" />
</template>
</NcActionButton>
<NcActionLink
v-if="episode.link"
:href="episode.link"
:name="t('repod', 'Open website')"
target="_blank"
:title="t('repod', 'Open website')">
<template #icon>
<OpenInNewIcon :size="20" />
</template>
</NcActionLink>
<NcActionLink
v-if="episode.url"
:href="episode.url"
:name="t('repod', 'Download')"
target="_blank"
:title="t('repod', 'Download')">
<template #icon>
<DownloadIcon :size="20" />
</template>
</NcActionLink>
</template>
2024-01-30 18:11:50 +01:00
<template #icon>
<NcAvatar
:display-name="episode.name"
2024-01-30 18:11:50 +01:00
:is-no-user="true"
:url="episode.image" />
</template>
<template #indicator>
<NcProgressBar
v-if="isListening(episode)"
2024-01-30 18:11:50 +01:00
class="progress"
:value="
(episode.action.position * 100) / episode.action.total
" />
2024-01-30 18:11:50 +01:00
</template>
<template #subname>
{{ episode.duration }}
</template>
2023-08-24 20:53:54 +02:00
</NcListItem>
</ul>
2024-01-18 10:12:55 +01:00
<NcModal v-if="modalEpisode" @close="modalEpisode = null">
<Modal
:description="modalEpisode.description"
2024-01-18 10:12:55 +01:00
:image="modalEpisode.image"
:link="modalEpisode.link"
:name="modalEpisode.name"
:size="modalEpisode.size"
:title="modalEpisode.title"
:url="modalEpisode.url" />
</NcModal>
2023-08-28 21:18:14 +02:00
</div>
2023-08-24 00:42:01 +02:00
</template>
<script>
import {
NcActionButton,
NcActionLink,
NcAvatar,
NcListItem,
NcModal,
NcProgressBar,
} from '@nextcloud/vue'
import {
durationToSeconds,
formatEpisodeTimestamp,
formatLocaleDate,
} from '../../utils/time.js'
import { mapActions, mapState } from 'pinia'
2024-03-16 18:35:31 +01:00
import DownloadIcon from 'vue-material-design-icons/Download.vue'
2023-12-23 22:49:23 +01:00
import Loading from '../Atoms/Loading.vue'
2024-01-17 22:18:32 +01:00
import Modal from '../Atoms/Modal.vue'
2024-03-16 18:35:31 +01:00
import OpenInNewIcon from 'vue-material-design-icons/OpenInNew.vue'
import PlayIcon from 'vue-material-design-icons/Play.vue'
import PlaylistPlayIcon from 'vue-material-design-icons/PlaylistPlay.vue'
import PlaylistRemoveIcon from 'vue-material-design-icons/PlaylistRemove.vue'
import StopIcon from 'vue-material-design-icons/Stop.vue'
2023-08-24 17:43:10 +02:00
import axios from '@nextcloud/axios'
2024-01-17 22:18:32 +01:00
import { decodeUrl } from '../../utils/url.js'
2023-08-24 17:43:10 +02:00
import { generateUrl } from '@nextcloud/router'
import { showError } from '../../utils/toast.js'
import { usePlayer } from '../../store/player.js'
import { useSettings } from '../../store/settings.js'
2023-08-24 17:43:10 +02:00
2023-08-24 00:42:01 +02:00
export default {
2023-08-30 09:27:12 +02:00
name: 'Episodes',
2023-08-24 17:43:10 +02:00
components: {
2024-03-16 18:35:31 +01:00
DownloadIcon,
2023-12-23 22:49:23 +01:00
Loading,
2023-12-24 16:59:34 +01:00
Modal,
2023-08-24 20:53:54 +02:00
NcActionButton,
NcActionLink,
2023-08-24 20:53:54 +02:00
NcAvatar,
2023-08-24 17:43:10 +02:00
NcListItem,
2023-12-24 16:34:27 +01:00
NcModal,
NcProgressBar,
2024-03-16 18:35:31 +01:00
OpenInNewIcon,
PlayIcon,
PlaylistPlayIcon,
PlaylistRemoveIcon,
StopIcon,
2023-08-24 17:43:10 +02:00
},
data() {
return {
episodes: [],
loading: true,
loadingAction: false,
2023-12-24 16:34:27 +01:00
modalEpisode: null,
2023-08-24 17:43:10 +02:00
}
},
computed: {
...mapState(usePlayer, ['episode']),
...mapState(useSettings, ['filters']),
filteredEpisodes() {
return this.episodes.filter((episode) => {
if (!this.filters.listened && this.hasEnded(episode)) {
return false
}
if (!this.filters.listening && this.isListening(episode)) {
return false
}
if (!this.filters.unlistened && !this.isListening(episode)) {
return false
}
return true
})
},
2023-08-24 17:43:10 +02:00
url() {
return decodeUrl(this.$route.params.url)
2023-08-24 17:43:10 +02:00
},
},
async mounted() {
try {
this.loading = true
const episodes = await axios.get(
generateUrl('/apps/repod/episodes/list?url={url}', {
url: this.url,
}),
)
this.episodes = [...episodes.data].sort(
(a, b) => new Date(b.pubDate?.date) - new Date(a.pubDate?.date),
)
2023-08-24 17:43:10 +02:00
} catch (e) {
console.error(e)
2024-01-10 15:25:54 +01:00
showError(t('repod', 'Could not fetch episodes'))
2023-08-24 17:43:10 +02:00
} finally {
this.loading = false
}
},
2023-08-24 20:53:54 +02:00
methods: {
...mapActions(usePlayer, ['load']),
formatLocaleDate,
hasEnded(episode) {
return (
episode.action &&
(episode.action.action === 'DELETE' ||
(episode.action.position > 0 &&
episode.action.total > 0 &&
episode.action.position >= episode.action.total))
)
},
2023-08-27 22:48:21 +02:00
isCurrentEpisode(episode) {
return this.currentEpisode && this.currentEpisode.url === episode.url
2023-08-27 22:48:21 +02:00
},
isListening(episode) {
return (
episode.action &&
episode.action.action.toLowerCase() === 'play' &&
!this.hasEnded(episode)
)
},
async markAs(episode, read) {
try {
this.loadingAction = true
episode.action = {
podcast: this.url,
episode: episode.url,
guid: episode.guid,
action: 'play',
timestamp: formatEpisodeTimestamp(new Date()),
started: episode.action ? episode.action.started : 0,
position: read ? durationToSeconds(episode.duration) : 0,
total: durationToSeconds(episode.duration),
2024-03-05 00:14:21 +01:00
}
await axios.post(
generateUrl('/apps/gpoddersync/episode_action/create'),
[episode.action],
)
this.updateList(episode)
} catch (e) {
console.error(e)
showError(t('repod', 'Could not change the status of the episode'))
} finally {
this.loadingAction = false
}
},
updateList(episode) {
this.episodes = this.episodes.map((e) =>
e.url === episode.url ? episode : e,
)
},
2023-08-24 20:53:54 +02:00
},
2023-08-24 00:42:01 +02:00
}
</script>
2023-08-30 19:59:20 +02:00
<style scoped>
.ended {
opacity: 0.4;
}
.progress {
margin-top: 0.4rem;
}
2023-08-30 19:59:20 +02:00
</style>