feat: add actions to episodes view
All checks were successful
repod / xml (push) Successful in 1m42s
repod / php (push) Successful in 35s
repod / nodejs (push) Successful in 2m51s
repod / release (push) Has been skipped

This commit is contained in:
Michel Roux 2024-03-04 16:55:16 +01:00
parent 5a510016a7
commit e52d20ffbc
4 changed files with 117 additions and 31 deletions

40
package-lock.json generated
View File

@ -43,12 +43,12 @@
} }
}, },
"node_modules/@ampproject/remapping": { "node_modules/@ampproject/remapping": {
"version": "2.2.1", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
"integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
"dependencies": { "dependencies": {
"@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.9" "@jridgewell/trace-mapping": "^0.3.24"
}, },
"engines": { "engines": {
"node": ">=6.0.0" "node": ">=6.0.0"
@ -2859,9 +2859,9 @@
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
}, },
"node_modules/@jridgewell/trace-mapping": { "node_modules/@jridgewell/trace-mapping": {
"version": "0.3.24", "version": "0.3.25",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.24.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
"integrity": "sha512-+VaWXDa6+l6MhflBvVXjIEAzb59nQ2JUK3bwRp2zRpPtU+8TFRy9Gg/5oIcNlkEL5PGlBFGfemUVvIgLnTzq7Q==", "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
"dependencies": { "dependencies": {
"@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14" "@jridgewell/sourcemap-codec": "^1.4.14"
@ -6058,9 +6058,9 @@
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001591", "version": "1.0.30001593",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001591.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001593.tgz",
"integrity": "sha512-PCzRMei/vXjJyL5mJtzNiUCKP59dm8Apqc3PH8gJkMnMXZGox93RbE76jHsmLwmIo6/3nsYIpJtx0O7u5PqFuQ==", "integrity": "sha512-UWM1zlo3cZfkpBysd7AS+z+v007q9G1+fLTUU42rQnY6t2axoogPW/xol6T7juU5EUoOhML4WgBIdG+9yYqAjQ==",
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@ -7259,9 +7259,9 @@
"peer": true "peer": true
}, },
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.4.689", "version": "1.4.690",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.689.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.690.tgz",
"integrity": "sha512-GatzRKnGPS1go29ep25reM94xxd1Wj8ritU0yRhCJ/tr1Bg8gKnm6R9O/yPOhGQBoLMZ9ezfrpghNaTw97C/PQ==" "integrity": "sha512-+2OAGjUx68xElQhydpcbqH50hE8Vs2K6TkAeLhICYfndb67CVH0UsZaijmRUE3rHlIxU1u0jxwhgVe6fK3YANA=="
}, },
"node_modules/elliptic": { "node_modules/elliptic": {
"version": "6.5.4", "version": "6.5.4",
@ -9670,9 +9670,9 @@
} }
}, },
"node_modules/html-entities": { "node_modules/html-entities": {
"version": "2.4.0", "version": "2.5.2",
"resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.4.0.tgz", "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz",
"integrity": "sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ==", "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@ -13708,9 +13708,9 @@
} }
}, },
"node_modules/node-polyfill-webpack-plugin/node_modules/type-fest": { "node_modules/node-polyfill-webpack-plugin/node_modules/type-fest": {
"version": "4.10.3", "version": "4.11.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.10.3.tgz", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.11.0.tgz",
"integrity": "sha512-JLXyjizi072smKGGcZiAJDCNweT8J+AuRxmPZ1aG7TERg4ijx9REl8CNhbr36RV4qXqL1gO1FF9HL8OkVmmrsA==", "integrity": "sha512-DPsoHKtnCUqqoB5Y4OPyat7ObSLz1XOkhHTmz+gOkz2p1xs+BBneTvHWriTwc313eozfBWh8b45EpaV3ZrrPPQ==",
"dev": true, "dev": true,
"peer": true, "peer": true,
"engines": { "engines": {

View File

@ -12,19 +12,63 @@
:title="episode.description" :title="episode.description"
@click="modalEpisode = episode"> @click="modalEpisode = episode">
<template #actions> <template #actions>
<NcActionButton v-if="!isCurrentEpisode(episode)" @click="load(episode)"> <NcActionButton v-if="!isCurrentEpisode(episode)"
:name="t('repod', 'Play')"
:title="t('repod', 'Play')"
@click="load(episode)">
<template #icon> <template #icon>
<PlayButton :size="20" /> <PlayButton :size="20" />
</template> </template>
{{ t('repod', 'Play') }}
</NcActionButton> </NcActionButton>
<NcActionButton v-if="isCurrentEpisode(episode)" @click="load(null)"> <NcActionButton v-if="isCurrentEpisode(episode)"
:name="t('repod', 'Stop')"
:title="t('repod', 'Stop')"
@click="load(null)">
<template #icon> <template #icon>
<StopButton :size="20" /> <StopButton :size="20" />
</template> </template>
{{ t('repod', 'Stop') }}
</NcActionButton> </NcActionButton>
</template> </template>
<template #extra>
<NcActions>
<NcActionButton v-if="episode.duration && !hasEnded(episode)"
:disabled="loadingAction"
:name="t('repod', 'Mark as read')"
:title="t('repod', 'Mark as read')"
@click="markAs(episode, true)">
<template #icon>
<PlaylistPlay :size="20" />
</template>
</NcActionButton>
<NcActionButton v-if="episode.duration && hasEnded(episode)"
:disabled="loadingAction"
:name="t('repod', 'Mark as unread')"
:title="t('repod', 'Mark as unread')"
@click="markAs(episode, false)">
<template #icon>
<PlaylistRemove :size="20" />
</template>
</NcActionButton>
<NcActionLink v-if="episode.link"
:href="episode.link"
:name="t('repod', 'Episode webpage')"
target="_blank"
:title="t('repod', 'Episode webpage')">
<template #icon>
<OpenInNew :size="20" />
</template>
</NcActionLink>
<NcActionLink v-if="episode.url"
:href="episode.url"
:name="t('repod', 'Download')"
target="_blank"
:title="t('repod', 'Download')">
<template #icon>
<Download :size="20" />
</template>
</NcActionLink>
</NcActions>
</template>
<template #icon> <template #icon>
<NcAvatar :display-name="episode.name" <NcAvatar :display-name="episode.name"
:is-no-user="true" :is-no-user="true"
@ -53,34 +97,45 @@
</template> </template>
<script> <script>
import { NcActionButton, NcAvatar, NcListItem, NcModal, NcProgressBar } from '@nextcloud/vue' import { NcActionButton, NcActionLink, NcActions, NcAvatar, NcListItem, NcModal, NcProgressBar } from '@nextcloud/vue'
import { durationToSeconds, formatEpisodeTimestamp, formatLocaleDate } from '../../utils/time.js'
import Download from 'vue-material-design-icons/Download.vue'
import Loading from '../Atoms/Loading.vue' import Loading from '../Atoms/Loading.vue'
import Modal from '../Atoms/Modal.vue' import Modal from '../Atoms/Modal.vue'
import OpenInNew from 'vue-material-design-icons/OpenInNew.vue'
import PlayButton from 'vue-material-design-icons/Play.vue' import PlayButton from 'vue-material-design-icons/Play.vue'
import PlaylistPlay from 'vue-material-design-icons/PlaylistPlay.vue'
import PlaylistRemove from 'vue-material-design-icons/PlaylistRemove.vue'
import StopButton from 'vue-material-design-icons/Stop.vue' import StopButton from 'vue-material-design-icons/Stop.vue'
import axios from '@nextcloud/axios' import axios from '@nextcloud/axios'
import { decodeUrl } from '../../utils/url.js' import { decodeUrl } from '../../utils/url.js'
import { formatLocaleDate } from '../../utils/time.js'
import { generateUrl } from '@nextcloud/router' import { generateUrl } from '@nextcloud/router'
import { showError } from '@nextcloud/dialogs' import { showError } from '@nextcloud/dialogs'
export default { export default {
name: 'Episodes', name: 'Episodes',
components: { components: {
Download,
Loading, Loading,
Modal, Modal,
NcActionButton, NcActionButton,
NcActionLink,
NcActions,
NcAvatar, NcAvatar,
NcListItem, NcListItem,
NcModal, NcModal,
NcProgressBar, NcProgressBar,
OpenInNew,
PlayButton, PlayButton,
PlaylistPlay,
PlaylistRemove,
StopButton, StopButton,
}, },
data() { data() {
return { return {
episodes: [], episodes: [],
loading: true, loading: true,
loadingAction: false,
modalEpisode: null, modalEpisode: null,
} }
}, },
@ -127,11 +182,10 @@ export default {
methods: { methods: {
formatLocaleDate, formatLocaleDate,
hasEnded(episode) { hasEnded(episode) {
return episode.action && (episode.action.action === 'DELETE' || ( return episode.action
episode.action.position > 0 && episode.action.position > 0
&& episode.action.total > 0 && episode.action.total > 0
&& episode.action.position >= episode.action.total && episode.action.position >= episode.action.total
))
}, },
isCurrentEpisode(episode) { isCurrentEpisode(episode) {
return this.currentEpisode && this.currentEpisode.url === episode.url return this.currentEpisode && this.currentEpisode.url === episode.url
@ -142,6 +196,26 @@ export default {
load(episode) { load(episode) {
this.$store.dispatch('player/load', episode) this.$store.dispatch('player/load', episode)
}, },
async markAs(episode, read) {
try {
this.loadingAction = true
await axios.post(generateUrl('/apps/gpoddersync/episode_action/create'), [{
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),
}])
} catch (e) {
console.error(e)
showError(t('repod', 'Could not change the status of the episode'))
} finally {
this.loadingAction = false
}
},
}, },
} }
</script> </script>

View File

@ -3,11 +3,10 @@
:name="feed ? feed.title : url" :name="feed ? feed.title : url"
:to="hash"> :to="hash">
<template #actions> <template #actions>
<NcActionButton @click="deleteSubscription"> <NcActionButton :name="t(`core`, 'Delete')" @click="deleteSubscription">
<template #icon> <template #icon>
<Delete :size="20" /> <Delete :size="20" />
</template> </template>
{{ t(`core`, 'Delete') }}
</NcActionButton> </NcActionButton>
</template> </template>
<template #icon> <template #icon>

View File

@ -37,3 +37,16 @@ export const formatEpisodeTimestamp = (date) => {
* @return {string} * @return {string}
*/ */
export const formatLocaleDate = (date) => date.toLocaleDateString(undefined, { dateStyle: 'medium' }) export const formatLocaleDate = (date) => date.toLocaleDateString(undefined, { dateStyle: 'medium' })
/**
* Returns the number of seconds from a duration feed's entry
* @param {string} duration The duration feed's entry
* @return {number}
*/
export const durationToSeconds = (duration) => {
const splitDuration = duration.split(':').reverse()
let seconds = splitDuration[0]
seconds += (splitDuration.length > 1) ? splitDuration[1] * 60 : 0
seconds += (splitDuration.length > 2) ? splitDuration[2] * 60 * 60 : 0
return seconds
}