diff --git a/front/src/components/audio/ChannelEntries.vue b/front/src/components/audio/ChannelEntries.vue index 84e097a554d11ed6da8aa802847627a16e9f8f07..1e176ac36cfccee80bd6301ad4f7490925d65d01 100644 --- a/front/src/components/audio/ChannelEntries.vue +++ b/front/src/components/audio/ChannelEntries.vue @@ -5,8 +5,22 @@ <div v-if="isLoading" class="ui inverted active dimmer"> <div class="ui loader"></div> </div> + <podcast-table + v-if="isPodcast" + :is-podcast="isPodcast" + :show-art="true" + :show-position="false" + :tracks="objects" + :show-artist="false" + :show-album="false" + :paginate-results="true" + :total="count" + @page-changed="updatePage" + :page="page" + :paginate-by="limit"></podcast-table> <track-table - :is-podcast="true" + v-else + :is-podcast="isPodcast" :show-art="true" :show-position="false" :tracks="objects" @@ -30,6 +44,7 @@ <script> import _ from '@/lodash' import axios from 'axios' +import PodcastTable from '@/components/audio/podcast/Table' import TrackTable from '@/components/audio/track/Table' export default { @@ -37,9 +52,11 @@ export default { filters: {type: Object, required: true}, limit: {type: Number, default: 10}, defaultCover: {type: Object}, + isPodcast: {type: Boolean, required: true}, }, components: { - TrackTable + PodcastTable, + TrackTable, }, data () { return { diff --git a/front/src/components/audio/PlayButton.vue b/front/src/components/audio/PlayButton.vue index 5ecdd8e5846ff6b9296f71f75e4c6c13f63e1e4a..dd0e651324a4f1d46200bc81f738f94c3408861a 100644 --- a/front/src/components/audio/PlayButton.vue +++ b/front/src/components/audio/PlayButton.vue @@ -33,7 +33,9 @@ <translate translate-context="Sidebar/Player/Icon.Tooltip/Verb">Add to playlist…</translate> </button> <button v-if="track" class="item basic" @click.stop.prevent="$router.push(`/library/tracks/${track.id}/`)"> - <i class="info icon"></i><translate translate-context="*/Queue/Dropdown/Button/Label/Short">Track details</translate> + <i class="info icon"></i> + <translate v-if="track.artist.content_category === 'podcast'" translate-context="*/Queue/Dropdown/Button/Label/Short">Episode details</translate> + <translate v-else translate-context="*/Queue/Dropdown/Button/Label/Short">Track details</translate> </button> <div class="divider"></div> <button v-if="filterableArtist" ref="filterArtist" data-ref="filterArtist" class="item basic" :disabled="!filterableArtist" @click.stop.prevent="filterArtist" :title="labels.hideArtist"> diff --git a/front/src/components/audio/podcast/MobileRow.vue b/front/src/components/audio/podcast/MobileRow.vue new file mode 100644 index 0000000000000000000000000000000000000000..9e7236e7af14addd4aab55106e70b185bad0a6e4 --- /dev/null +++ b/front/src/components/audio/podcast/MobileRow.vue @@ -0,0 +1,195 @@ +<template> + <div + :class="[ + { active: currentTrack && track.id === currentTrack.id }, + 'track-row row mobile', + ]" + > + <div + v-if="showArt" + @click.prevent.exact="activateTrack(track, index)" + class="image left floated column" + > + <img + alt="" + class="ui artist-track mini image" + v-if=" + track.album && track.album.cover && track.album.cover.urls.original + " + v-lazy=" + $store.getters['instance/absoluteUrl']( + track.album.cover.urls.medium_square_crop + ) + " + /> + <img + alt="" + class="ui artist-track mini image" + v-else-if=" + track.cover + " + v-lazy=" + $store.getters['instance/absoluteUrl']( + track.cover.urls.medium_square_crop + ) + " + /> + <img + alt="" + class="ui artist-track mini image" + v-else-if=" + track.artist.cover + " + v-lazy=" + $store.getters['instance/absoluteUrl']( + track.artist.cover.urls.medium_square_crop + ) + " + /> + <img + alt="" + class="ui artist-track mini image" + v-else + src="../../../assets/audio/default-cover.png" + /> + </div> + <div + tabindex=0 + @click="activateTrack(track, index)" + role="button" + class="content ellipsis left floated column" + > + <p + :class="[ + 'track-title', + 'mobile', + { 'play-indicator': isPlaying && currentTrack && track.id === currentTrack.id }, + ]" + > + {{ track.title }} + </p> + <p v-if="track.artist.content_category === 'podcast'" class="track-meta mobile"> + <human-date class="really discrete" :date="track.creation_date"></human-date> + <span>·</span> + <human-duration + v-if="track.uploads[0] && track.uploads[0].duration" + :duration="track.uploads[0].duration" + ></human-duration> + </p> + <p v-else class="track-meta mobile"> + {{ track.artist.name }} <span>·</span> + <human-duration + v-if="track.uploads[0] && track.uploads[0].duration" + :duration="track.uploads[0].duration" + ></human-duration> + </p> + </div> + <div + v-if="$store.state.auth.authenticated && this.track.artist.content_category !== 'podcast'" + :class="[ + 'meta', + 'right', + 'floated', + 'column', + 'mobile', + { 'with-art': showArt }, + ]" + role="button" + > + <track-favorite-icon + class="tiny" + :border="false" + :track="track" + ></track-favorite-icon> + </div> + <div + role="button" + :aria-label="actionsButtonLabel" + @click.prevent.exact="showTrackModal = !showTrackModal" + :class="[ + 'modal-button', + 'right', + 'floated', + 'column', + 'mobile', + { 'with-art': showArt }, + ]" + > + <i class="ellipsis large vertical icon" /> + </div> + <track-modal + @update:show="showTrackModal = $event;" + :show="showTrackModal" + :track="track" + :index="index" + :is-artist="isArtist" + :is-album="isAlbum" + ></track-modal> + </div> +</template> + +<script> +import PlayIndicator from "@/components/audio/track/PlayIndicator"; +import { mapActions, mapGetters } from "vuex"; +import TrackFavoriteIcon from "@/components/favorites/TrackFavoriteIcon"; +import TrackModal from "@/components/audio/track/Modal"; +import PlayOptionsMixin from "@/components/mixins/PlayOptions" + +export default { + mixins: [PlayOptionsMixin], + data() { + return { + showTrackModal: false, + } + }, + props: { + tracks: Array, + showAlbum: { type: Boolean, required: false, default: true }, + showArtist: { type: Boolean, required: false, default: true }, + showPosition: { type: Boolean, required: false, default: false }, + showArt: { type: Boolean, required: false, default: true }, + search: { type: Boolean, required: false, default: false }, + filters: { type: Object, required: false, default: null }, + nextUrl: { type: String, required: false, default: null }, + displayActions: { type: Boolean, required: false, default: true }, + showDuration: { type: Boolean, required: false, default: true }, + index: { type: Number, required: true }, + track: { type: Object, required: true }, + isArtist: {type: Boolean, required: false, default: false}, + isAlbum: {type: Boolean, required: false, default: false}, + }, + + components: { + PlayIndicator, + TrackFavoriteIcon, + TrackModal, + }, + computed: { + ...mapGetters({ + currentTrack: "queue/currentTrack", + }), + + isPlaying() { + return this.$store.state.player.playing; + }, + actionsButtonLabel () { + return this.$pgettext('Content/Track/Icon.Tooltip/Verb', 'Show track actions') + }, + }, + + methods: { + prettyPosition(position, size) { + var s = String(position); + while (s.length < (size || 2)) { + s = "0" + s; + } + return s; + }, + + ...mapActions({ + resumePlayback: "player/resumePlayback", + pausePlayback: "player/pausePlayback", + }), + }, +}; +</script> diff --git a/front/src/components/audio/podcast/Modal.vue b/front/src/components/audio/podcast/Modal.vue new file mode 100644 index 0000000000000000000000000000000000000000..dae3275ccc11a6527c42211ecdbe3d7a1605b570 --- /dev/null +++ b/front/src/components/audio/podcast/Modal.vue @@ -0,0 +1,304 @@ +<template> + <modal + @update:show="$emit('update:show', $event)" + :show="show" + :scrolling="true" + :additionalClasses="['scrolling-track-options']" + > + <div class="header"> + <div class="ui large centered rounded image"> + <img + alt="" + class="ui centered image" + v-if=" + track.album && track.album.cover && track.album.cover.urls.original + " + v-lazy=" + $store.getters['instance/absoluteUrl']( + track.album.cover.urls.medium_square_crop + ) + " + /> + <img + alt="" + class="ui centered image" + v-else-if="track.cover" + v-lazy=" + $store.getters['instance/absoluteUrl']( + track.cover.urls.medium_square_crop + ) + " + /> + <img + alt="" + class="ui centered image" + v-else-if="track.artist.cover" + v-lazy=" + $store.getters['instance/absoluteUrl']( + track.artist.cover.urls.medium_square_crop + ) + " + /> + <img + alt="" + class="ui centered image" + v-else + src="../../../assets/audio/default-cover.png" + /> + </div> + <h3 class="track-modal-title">{{ track.title }}</h3> + <h4 class="track-modal-subtitle">{{ track.artist.name }}</h4> + </div> + <div class="ui hidden divider"></div> + <div class="content"> + <div class="ui one column unstackable grid"> + <div + class="row" + v-if="$store.state.auth.authenticated && this.track.artist.content_category !== 'podcast'"> + <div + tabindex="0" + class="column" + role="button" + :aria-label="favoriteButton" + @click.stop="$store.dispatch('favorites/toggle', track.id)" + > + <i + :class="[ + 'heart', + 'favorite-icon', + { favorited: isFavorite }, + { pink: isFavorite }, + 'icon', + 'track-modal', + 'list-icon', + ]" + /> + <span class="track-modal list-item">{{ favoriteButton }}</span> + </div> + </div> + <div class="row"> + <div + class="column" + role="button" + @click.stop.prevent=" + add(); + closeModal(); + " + :aria-label="labels.addToQueue" + > + <i class="plus icon track-modal list-icon" /> + <span class="track-modal list-item">{{ labels.addToQueue }}</span> + </div> + </div> + <div class="row"> + <div + class="column" + role="button" + @click.stop.prevent=" + addNext(true); + closeModal(); + " + :aria-label="labels.playNext" + > + <i class="step forward icon track-modal list-icon" /> + <span class="track-modal list-item">{{ labels.playNext }}</span> + </div> + </div> + <div class="row"> + <div + class="column" + role="button" + @click.stop.prevent=" + $store.dispatch('radios/start', { + type: 'similar', + objectId: track.id, + }); + closeModal(); + " + :aria-label="labels.startRadio" + > + <i class="rss icon track-modal list-icon" /> + <span class="track-modal list-item">{{ labels.startRadio }}</span> + </div> + </div> + <div class="row"> + <div + class="column" + role="button" + @click.stop="$store.commit('playlists/chooseTrack', track)" + :aria-label="labels.addToPlaylist" + > + <i class="list icon track-modal list-icon" /> + <span class="track-modal list-item">{{ + labels.addToPlaylist + }}</span> + </div> + </div> + <div class="ui divider"></div> + <div v-if="!isAlbum && track.album" class="row"> + <div + class="column" + role="button" + :aria-label="albumDetailsButton" + @click.prevent.exact=" + $router.push({ + name: 'library.albums.detail', + params: { id: track.album.id }, + }) + " + > + <i class="compact disc icon track-modal list-icon" /> + <span class="track-modal list-item">{{ + albumDetailsButton + }}</span> + </div> + </div> + <div v-if="!isArtist" class="row"> + <div + class="column" + role="button" + :aria-label="artistDetailsButton" + @click.prevent.exact=" + $router.push({ + name: 'library.artists.detail', + params: { id: track.artist.id }, + }) + " + > + <i class="user icon track-modal list-icon" /> + <span class="track-modal list-item">{{ + artistDetailsButton + }}</span> + </div> + </div> + <div class="row"> + <div + class="column" + role="button" + :aria-label="trackDetailsButton" + @click.prevent.exact=" + $router.push({ + name: 'library.tracks.detail', + params: { id: track.id }, + }) + " + > + <i class="info icon track-modal list-icon" /> + <span class="track-modal list-item">{{ + trackDetailsButton + }}</span> + </div> + </div> + <div class="ui divider"></div> + <div + v-for="obj in getReportableObjs({ + track, + album, + artist, + })" + :key="obj.target.type + obj.target.id" + class="row" + :ref="`report${obj.target.type}${obj.target.id}`" + :data-ref="`report${obj.target.type}${obj.target.id}`" + @click.stop.prevent="$store.dispatch('moderation/report', obj.target)" + > + <div class="column"> + <i class="share icon track-modal list-icon" /><span + class="track-modal list-item" + >{{ obj.label }}</span + > + </div> + </div> + </div> + </div> + </modal> +</template> + +<script> +import Modal from "@/components/semantic/Modal"; +import TrackFavoriteIcon from "@/components/favorites/TrackFavoriteIcon"; +import ReportMixin from '@/components/mixins/Report' +import PlayOptionsMixin from '@/components/mixins/PlayOptions' + +export default { + mixins: [ReportMixin, PlayOptionsMixin], + props: { + show: { type: Boolean, required: true, default: false }, + track: { type: Object, required: true }, + index: { type: Number, required: true }, + isArtist: { type: Boolean, required: false, default: false }, + isAlbum: { type: Boolean, required: false, default: false }, + }, + components: { + Modal, + TrackFavoriteIcon, + }, + data() { + return { + isShowing: this.show, + tracks: [this.track], + album: this.track.album, + artist: this.track.artist, + }; + }, + computed: { + isFavorite() { + return this.$store.getters["favorites/isFavorite"](this.track.id); + }, + favoriteButton() { + if (this.isFavorite) { + return this.$pgettext( + "Content/Track/Icon.Tooltip/Verb", + "Remove from favorites" + ); + } else { + return this.$pgettext("Content/Track/*/Verb", "Add to favorites"); + } + }, + trackDetailsButton() { + if (this.track.artist.content_category === 'podcast') { + return this.$pgettext("*/Queue/Dropdown/Button/Label/Short", "Episode details") + } else { + return this.$pgettext("*/Queue/Dropdown/Button/Label/Short", "Track details") + } + }, + albumDetailsButton() { + if (this.track.artist.content_category === 'podcast') { + return this.$pgettext("*/Queue/Dropdown/Button/Label/Short", "View series") + } else { + return this.$pgettext("*/Queue/Dropdown/Button/Label/Short", "View album") + } + }, + artistDetailsButton() { + if (this.track.artist.content_category === 'podcast') { + return this.$pgettext("*/Queue/Dropdown/Button/Label/Short", "View channel") + } else { + return this.$pgettext("*/Queue/Dropdown/Button/Label/Short", "View artist") + } + }, + labels() { + return { + startRadio: this.$pgettext( + "*/Queue/Dropdown/Button/Title", + "Play radio" + ), + playNow: this.$pgettext("*/Queue/Dropdown/Button/Title", "Play now"), + addToQueue: this.$pgettext( + "*/Queue/Dropdown/Button/Title", + "Add to queue" + ), + playNext: this.$pgettext("*/Queue/Dropdown/Button/Title", "Play next"), + addToPlaylist: this.$pgettext( + "Sidebar/Player/Icon.Tooltip/Verb", + "Add to playlist…" + ), + }; + }, + }, + methods: { + closeModal() { + this.$emit("update:show", false); + }, + }, +}; +</script> diff --git a/front/src/components/audio/podcast/Row.vue b/front/src/components/audio/podcast/Row.vue new file mode 100644 index 0000000000000000000000000000000000000000..3e6155b67ea56e26f9f913674331547e0ea6af85 --- /dev/null +++ b/front/src/components/audio/podcast/Row.vue @@ -0,0 +1,141 @@ +<template> + <div + :class="[ + { active: currentTrack && track.id === currentTrack.id }, + 'track-row podcast row', + ]" + @mouseover="hover = track.id" + @mouseleave="hover = null" + @dblclick="activateTrack(track, index)" + > + <div + v-if="showArt" + class="image left floated column" + role="button" + @click.prevent.exact="activateTrack(track, index)" + > + <img + alt="" + class="ui artist-track mini image" + v-if=" + track.album && track.album.cover && track.album.cover.urls.original + " + v-lazy=" + $store.getters['instance/absoluteUrl']( + track.album.cover.urls.medium_square_crop + ) + " + /> + <img + alt="" + class="ui artist-track mini image" + v-else-if=" + track.cover && track.cover.urls.original + " + v-lazy=" + $store.getters['instance/absoluteUrl']( + track.cover.urls.medium_square_crop + ) + " + /> + <img + alt="" + class="ui artist-track mini image" + v-else-if=" + track.artist && track.artist.cover && track.album.cover.urls.original + " + v-lazy=" + $store.getters['instance/absoluteUrl']( + track.cover.urls.medium_square_crop + ) + " + /> + <img + alt="" + class="ui artist-track mini image" + v-else + src="../../../assets/audio/default-cover.png" + /> + </div> + <div tabindex=0 class="content left floated column"> + <p class="podcast-episode-title ellipsis">{{ track.title }}</p> + <p class="podcast-episode-meta"> + An episode description, with all its twists and turns! + This episode focuses on something I'm sure, but nobody really knows what it's focusing on.</p> + </div> + <div v-if="displayActions" class="meta right floated column"> + <play-button + id="playmenu" + class="play-button basic icon" + :dropdown-only="true" + :is-playable="track.is_playable" + :dropdown-icon-classes="[ + 'ellipsis', + 'vertical', + 'large really discrete', + ]" + :track="track" + ></play-button> + </div> + </div> +</template> + +<script> +import PlayIndicator from "@/components/audio/track/PlayIndicator"; +import { mapActions, mapGetters } from "vuex"; +import PlayButton from "@/components/audio/PlayButton"; + +export default { + props: { + tracks: Array, + showAlbum: { type: Boolean, required: false, default: true }, + showArtist: { type: Boolean, required: false, default: true }, + showPosition: { type: Boolean, required: false, default: false }, + showArt: { type: Boolean, required: false, default: true }, + search: { type: Boolean, required: false, default: false }, + filters: { type: Object, required: false, default: null }, + nextUrl: { type: String, required: false, default: null }, + displayActions: { type: Boolean, required: false, default: true }, + showDuration: { type: Boolean, required: false, default: true }, + index: { type: Number, required: true }, + track: { type: Object, required: true }, + }, + + data() { + return { + hover: null, + } + }, + + components: { + PlayIndicator, + PlayButton, + }, + + computed: { + ...mapGetters({ + currentTrack: "queue/currentTrack", + }), + + isPlaying() { + return this.$store.state.player.playing; + }, + }, + + methods: { + + prettyPosition(position, size) { + var s = String(position); + while (s.length < (size || 2)) { + s = "0" + s; + } + return s; + }, + + ...mapActions({ + resumePlayback: "player/resumePlayback", + pausePlayback: "player/pausePlayback", + }), + }, +}; +</script> diff --git a/front/src/components/audio/podcast/Table.vue b/front/src/components/audio/podcast/Table.vue new file mode 100644 index 0000000000000000000000000000000000000000..134a2432364103e6c5560e89f51e595723d6309b --- /dev/null +++ b/front/src/components/audio/podcast/Table.vue @@ -0,0 +1,125 @@ +<template> + <div> + <div class="ui hidden divider"></div> + + <!-- Add a header if needed --> + + <slot name="header"></slot> + + <div> + <div + :class="['track-table', 'ui', 'unstackable', 'grid', 'tablet-and-up']" + > + <!-- For each item, build a row --> + <podcast-row + v-for="(track, index) in tracks" + :track="track" + :key="track.id" + :index="index" + :tracks="tracks" + :display-actions="displayActions" + :show-duration="showDuration" + :is-podcast="isPodcast" + ></podcast-row> + </div> + <div v-if="paginateResults" class="ui center aligned basic segment desktop-and-up"> + <pagination + :total="total" + :current="page" + :paginate-by="paginateBy" + v-on="$listeners"> + </pagination> + </div> + </div> + + <div + :class="['track-table', 'ui', 'unstackable', 'grid', 'tablet-and-below']" + > + <div v-if="isLoading" class="ui inverted active dimmer"> + <div class="ui loader"></div> + </div> + + <!-- For each item, build a row --> + + <track-mobile-row + v-for="(track, index) in allTracks" + :track="track" + :key="track.id" + :index="index" + :tracks="allTracks" + :show-position="showPosition" + :show-art="showArt" + :show-duration="showDuration" + :is-artist="isArtist" + :is-album="isAlbum" + :is-podcast="isPodcast" + ></track-mobile-row> + <div v-if="paginateResults" class="ui center aligned basic segment tablet-and-below"> + <pagination + v-if="paginateResults" + :total="total" + :current="page" + :compact="true" + v-on="$listeners"></pagination> + </div> + </div> + </div> +</template> + +<script> +import _ from "@/lodash"; +import TrackRow from "@/components/audio/track/Row"; +import PodcastRow from "@/components/audio/podcast/Row"; +import TrackMobileRow from "@/components/audio/track/MobileRow"; +import Pagination from "@/components/Pagination"; + +export default { + components: { + TrackRow, + TrackMobileRow, + Pagination, + PodcastRow, + }, + + props: { + tracks: Array, + showAlbum: { type: Boolean, required: false, default: true }, + showArtist: { type: Boolean, required: false, default: true }, + showPosition: { type: Boolean, required: false, default: false }, + showArt: { type: Boolean, required: false, default: true }, + search: { type: Boolean, required: false, default: false }, + filters: { type: Object, required: false, default: null }, + nextUrl: { type: String, required: false, default: null }, + displayActions: { type: Boolean, required: false, default: true }, + showDuration: { type: Boolean, required: false, default: true }, + isArtist: { type: Boolean, required: false, default: false }, + isAlbum: { type: Boolean, required: false, default: false }, + paginateResults: { type: Boolean, required: false, default: true}, + total: { type: Number, required: false}, + page: {type: Number, required: false, default: 1}, + paginateBy: {type: Number, required: false, default: 25}, + isPodcast: {type: Boolean, required: true}, + }, + + data() { + return { + isLoading: false, + }; + }, + + computed: { + labels() { + return { + title: this.$pgettext("*/*/*/Noun", "Title"), + album: this.$pgettext("*/*/*/Noun", "Album"), + artist: this.$pgettext("*/*/*/Noun", "Artist"), + }; + }, + }, + methods: { + updatePage: function(page) { + this.$emit('page-changed', page) + } + }, +}; +</script> diff --git a/front/src/components/audio/track/MobileRow.vue b/front/src/components/audio/track/MobileRow.vue index 1d24cbda7ee4f3594b98a04447a30a73603d1760..da2b1f3d28033397692f3f7d9da6307d64a5b3ee 100644 --- a/front/src/components/audio/track/MobileRow.vue +++ b/front/src/components/audio/track/MobileRow.vue @@ -5,36 +5,6 @@ 'track-row row mobile', ]" > - <!-- - <div - class="actions one wide left floated column" - role="button" - @click.prevent.exact="activateTrack(track, index)" - v-if="!showArt"> - <play-indicator - v-if=" - !$store.state.player.isLoadingAudio && - currentTrack && - isPlaying && - track.id === currentTrack.id - " - > - </play-indicator> - <button - v-else-if=" - currentTrack && - !isPlaying && - track.id === currentTrack.id - " - class="ui really tiny basic icon button play-button paused" - > - <i class="pause icon" /> - </button> - <span class="track-position" v-else-if="showPosition"> - {{ prettyPosition(track.position) }} - </span> - </div> - --> <div v-if="showArt" @click.prevent.exact="activateTrack(track, index)" @@ -126,7 +96,7 @@ </div> <div role="button" - aria-label="Show Details" + :aria-label="actionsButtonLabel" @click.prevent.exact="showTrackModal = !showTrackModal" :class="[ 'modal-button', @@ -168,7 +138,6 @@ export default { tracks: Array, showAlbum: { type: Boolean, required: false, default: true }, showArtist: { type: Boolean, required: false, default: true }, - trackOnly: { type: Boolean, required: false, default: false }, showPosition: { type: Boolean, required: false, default: false }, showArt: { type: Boolean, required: false, default: true }, search: { type: Boolean, required: false, default: false }, @@ -195,6 +164,9 @@ export default { isPlaying() { return this.$store.state.player.playing; }, + actionsButtonLabel () { + return this.$pgettext('Content/Track/Icon.Tooltip/Verb', 'Show track actions') + }, }, methods: { diff --git a/front/src/components/audio/track/Modal.vue b/front/src/components/audio/track/Modal.vue index 1c3e17440cd15ac65f6dd77174be16abdd466080..dae3275ccc11a6527c42211ecdbe3d7a1605b570 100644 --- a/front/src/components/audio/track/Modal.vue +++ b/front/src/components/audio/track/Modal.vue @@ -52,11 +52,14 @@ <div class="ui hidden divider"></div> <div class="content"> <div class="ui one column unstackable grid"> - <div class="row"> + <div + class="row" + v-if="$store.state.auth.authenticated && this.track.artist.content_category !== 'podcast'"> <div tabindex="0" class="column" role="button" + :aria-label="favoriteButton" @click.stop="$store.dispatch('favorites/toggle', track.id)" > <i @@ -132,11 +135,11 @@ </div> </div> <div class="ui divider"></div> - <div v-if="!isAlbum" class="row"> + <div v-if="!isAlbum && track.album" class="row"> <div class="column" role="button" - :aria-label="labels.albumDetailsButton" + :aria-label="albumDetailsButton" @click.prevent.exact=" $router.push({ name: 'library.albums.detail', @@ -146,7 +149,7 @@ > <i class="compact disc icon track-modal list-icon" /> <span class="track-modal list-item">{{ - labels.albumDetailsButton + albumDetailsButton }}</span> </div> </div> @@ -154,7 +157,7 @@ <div class="column" role="button" - :aria-label="labels.artistDetailsButton" + :aria-label="artistDetailsButton" @click.prevent.exact=" $router.push({ name: 'library.artists.detail', @@ -164,7 +167,7 @@ > <i class="user icon track-modal list-icon" /> <span class="track-modal list-item">{{ - labels.artistDetailsButton + artistDetailsButton }}</span> </div> </div> @@ -172,7 +175,7 @@ <div class="column" role="button" - :aria-label="labels.trackDetailsButton" + :aria-label="trackDetailsButton" @click.prevent.exact=" $router.push({ name: 'library.tracks.detail', @@ -182,7 +185,7 @@ > <i class="info icon track-modal list-icon" /> <span class="track-modal list-item">{{ - labels.trackDetailsButton + trackDetailsButton }}</span> </div> </div> @@ -252,20 +255,29 @@ export default { return this.$pgettext("Content/Track/*/Verb", "Add to favorites"); } }, + trackDetailsButton() { + if (this.track.artist.content_category === 'podcast') { + return this.$pgettext("*/Queue/Dropdown/Button/Label/Short", "Episode details") + } else { + return this.$pgettext("*/Queue/Dropdown/Button/Label/Short", "Track details") + } + }, + albumDetailsButton() { + if (this.track.artist.content_category === 'podcast') { + return this.$pgettext("*/Queue/Dropdown/Button/Label/Short", "View series") + } else { + return this.$pgettext("*/Queue/Dropdown/Button/Label/Short", "View album") + } + }, + artistDetailsButton() { + if (this.track.artist.content_category === 'podcast') { + return this.$pgettext("*/Queue/Dropdown/Button/Label/Short", "View channel") + } else { + return this.$pgettext("*/Queue/Dropdown/Button/Label/Short", "View artist") + } + }, labels() { return { - trackDetailsButton: this.$pgettext( - "*/Queue/Dropdown/Button/Label/Short", - "Track details" - ), - albumDetailsButton: this.$pgettext( - "*/Queue/Dropdown/Button/Label/Short", - "View album" - ), - artistDetailsButton: this.$pgettext( - "*/Queue/Dropdown/Button/Label/Short", - "View artist" - ), startRadio: this.$pgettext( "*/Queue/Dropdown/Button/Title", "Play radio" @@ -280,10 +292,6 @@ export default { "Sidebar/Player/Icon.Tooltip/Verb", "Add to playlist…" ), - addToQueue: this.$pgettext( - "*/Queue/Dropdown/Button/Title", - "Add to queue" - ), }; }, }, diff --git a/front/src/components/audio/track/Row.vue b/front/src/components/audio/track/Row.vue index 87d2724bd040a80ea3aec567f6da7dd328fb93fe..2dbae9435fcd582efc9e1edef9299f33836dc152 100644 --- a/front/src/components/audio/track/Row.vue +++ b/front/src/components/audio/track/Row.vue @@ -106,20 +106,10 @@ </div> <div tabindex=0 class="content ellipsis left floated column"> <a - v-if="currentTrack && !isPlaying && track.id === currentTrack.id" - @click="resumePlayback" + @click="activateTrack(track, index)" > {{ track.title }} </a> - <a - v-else-if="currentTrack && isPlaying && track.id === currentTrack.id" - @click="pausePlayback" - > - {{ track.title }} - </a> - <a v-else @click.prevent.exact="replacePlay(tracks, index)"> - {{ track.title }} - </a> </div> <div v-if="showAlbum" class="content ellipsis left floated column"> <router-link @@ -175,13 +165,14 @@ import PlayIndicator from "@/components/audio/track/PlayIndicator"; import { mapActions, mapGetters } from "vuex"; import TrackFavoriteIcon from "@/components/favorites/TrackFavoriteIcon"; import PlayButton from "@/components/audio/PlayButton"; +import PlayOptions from "@/components/mixins/PlayOptions" export default { + mixins: [PlayOptions], props: { tracks: Array, showAlbum: { type: Boolean, required: false, default: true }, showArtist: { type: Boolean, required: false, default: true }, - trackOnly: { type: Boolean, required: false, default: false }, showPosition: { type: Boolean, required: false, default: false }, showArt: { type: Boolean, required: false, default: true }, search: { type: Boolean, required: false, default: false }, @@ -216,12 +207,6 @@ export default { }, methods: { - replacePlay(tracks, trackIndex) { - this.$store.dispatch("queue/clean"); - this.$store.dispatch("queue/appendMany", { tracks: tracks }).then(() => { - this.$store.dispatch("queue/currentIndex", trackIndex); - }); - }, prettyPosition(position, size) { var s = String(position); @@ -230,25 +215,7 @@ export default { } return s; }, - - activateTrack(track, index) { - if ( - this.currentTrack && - this.isPlaying && - track.id === this.currentTrack.id - ) { - this.pausePlayback(); - } else if ( - this.currentTrack && - !this.isPlaying && - track.id === this.currentTrack.id - ) { - this.resumePlayback(); - } else { - this.replacePlay(this.tracks, index); - } - }, - + ...mapActions({ resumePlayback: "player/resumePlayback", pausePlayback: "player/pausePlayback", diff --git a/front/src/components/library/AlbumDetail.vue b/front/src/components/library/AlbumDetail.vue index 5bd30b73e65bda2ba528b0f375f7332e7b715210..a584a372a63bc9d0b6ccfc46be7798c8467c995a 100644 --- a/front/src/components/library/AlbumDetail.vue +++ b/front/src/components/library/AlbumDetail.vue @@ -4,7 +4,7 @@ <translate key="1" v-if="isSerie" translate-context="Content/Channels/*">Episodes</translate> <translate key="2" v-else translate-context="*/*/*">Tracks</translate> </h2> - <channel-entries v-if="artist.channel && isSerie" :limit="50" :filters="{channel: artist.channel.uuid, album: object.id, ordering: '-creation_date'}"> + <channel-entries v-if="artist.channel && isSerie" :is-podcast="isSerie" :limit="50" :filters="{channel: artist.channel.uuid, album: object.id, ordering: '-creation_date'}"> </channel-entries> <template v-else-if="discs && discs.length > 1"> <div v-for="tracks in discs" :key="tracks.disc_number"> diff --git a/front/src/style/components/_track_table.scss b/front/src/style/components/_track_table.scss index 6d8f8a3a96ae2827c6695463ad94406c040eb028..7dfde12462a0e1c25e6f9f2586c9e707ff812ef4 100644 --- a/front/src/style/components/_track_table.scss +++ b/front/src/style/components/_track_table.scss @@ -209,4 +209,35 @@ width: 0px; background: transparent; } +} + +.track-table.podcast.row, +.track-row.podcast.row { + height: 20vh; + align-items: center; + display: flex; + .ui.artist-track.mini.image { + height: 15vh; + width: auto; + top: auto; + bottom: auto; + } + .image.left.floated.column { + width: (15vh); + display: flex !important; + justify-content: center; + flex-direction: column; + } + .content.left.floated.column { + margin-left: 26px; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + + .podcast-episode-title { + font-weight: bold; + font-size: medium; + } + } } \ No newline at end of file diff --git a/front/src/views/channels/DetailOverview.vue b/front/src/views/channels/DetailOverview.vue index 7c37fdbd23e103490ca92527f012f2f3b9071ece..54c53a9b4cf03c93175c2dc29e08681c3d7509a4 100644 --- a/front/src/views/channels/DetailOverview.vue +++ b/front/src/views/channels/DetailOverview.vue @@ -49,7 +49,7 @@ :can-update="false"></rendered-description> <div class="ui hidden divider"></div> </div> - <channel-entries :key="String(episodesKey) + 'entries'" :default-cover='object.artist.cover' :limit='25' :filters="{channel: object.uuid, ordering: '-creation_date', page_size: '25'}"> + <channel-entries :is-podcast="isPodcast" :key="String(episodesKey) + 'entries'" :default-cover='object.artist.cover' :limit='25' :filters="{channel: object.uuid, ordering: '-creation_date', page_size: '25'}"> <h2 class="ui header"> <translate key="1" v-if="isPodcast" translate-context="Content/Channel/Paragraph">Latest episodes</translate> <translate key="2" v-else translate-context="Content/Channel/Paragraph">Latest tracks</translate>