[web] Streamline Spotify pages to match library pages

This commit is contained in:
Alain Nussbaumer 2024-03-25 21:54:01 +01:00
parent 8b1c4decf5
commit 6fd4db14fb
12 changed files with 147 additions and 166 deletions

File diff suppressed because one or more lines are too long

View File

@ -1,34 +1,38 @@
<template> <template>
<div class="media is-align-items-center" @click="open"> <template v-for="item in items" :key="item.id">
<div v-if="show_artwork" class="media-left is-clickable"> <div class="media is-align-items-center" @click="open(item)">
<cover-artwork <div v-if="show_artwork" class="media-left is-clickable">
:artwork_url="artwork_url" <cover-artwork
:artist="item.artist" :artwork_url="artwork_url(item)"
:album="item.name" :artist="item.artist"
class="is-clickable fd-has-shadow fd-cover fd-cover-small-image" :album="item.name"
/> class="is-clickable fd-has-shadow fd-cover fd-cover-small-image"
/>
</div>
<div class="media-content is-clickable is-clipped">
<h1 class="title is-6" v-text="item.name" />
<h2
class="subtitle is-7 has-text-grey has-text-weight-bold"
v-text="item.artists[0]?.name"
/>
<h2
class="subtitle is-7 has-text-grey"
v-text="
[item.album_type, $filters.date(item.release_date)].join(', ')
"
/>
</div>
<div class="media-right">
<a @click.prevent.stop="open_dialog(item)">
<mdicon class="icon has-text-dark" name="dots-vertical" size="16" />
</a>
</div>
</div> </div>
<div class="media-content is-clickable is-clipped"> </template>
<h1 class="title is-6" v-text="item.name" />
<h2
class="subtitle is-7 has-text-grey has-text-weight-bold"
v-text="item.artists[0]?.name"
/>
<h2
class="subtitle is-7 has-text-grey"
v-text="[item.album_type, $filters.date(item.release_date)].join(', ')"
/>
</div>
<div class="media-right">
<a @click.prevent.stop="show_details_modal = true">
<mdicon class="icon has-text-dark" name="dots-vertical" size="16" />
</a>
</div>
</div>
<teleport to="#app"> <teleport to="#app">
<modal-dialog-album-spotify <modal-dialog-album-spotify
:show="show_details_modal" :show="show_details_modal"
:album="item" :album="selected_item"
@close="show_details_modal = false" @close="show_details_modal = false"
/> />
</teleport> </teleport>
@ -41,16 +45,13 @@ import ModalDialogAlbumSpotify from '@/components/ModalDialogAlbumSpotify.vue'
export default { export default {
name: 'ListItemAlbumSpotify', name: 'ListItemAlbumSpotify',
components: { CoverArtwork, ModalDialogAlbumSpotify }, components: { CoverArtwork, ModalDialogAlbumSpotify },
props: { item: { required: true, type: Object } }, props: { items: { required: true, type: Object } },
data() { data() {
return { show_details_modal: false } return { selected_item: {}, show_details_modal: false }
}, },
computed: { computed: {
artwork_url() {
return this.item.images?.[0]?.url ?? ''
},
show_artwork() { show_artwork() {
return this.$store.getters.settings_option( return this.$store.getters.settings_option(
'webinterface', 'webinterface',
@ -60,11 +61,18 @@ export default {
}, },
methods: { methods: {
open() { artwork_url(item) {
return item.images?.[0]?.url ?? ''
},
open(item) {
this.$router.push({ this.$router.push({
name: 'music-spotify-album', name: 'music-spotify-album',
params: { id: this.item.id } params: { id: item.id }
}) })
},
open_dialog(item) {
this.selected_item = item
this.show_details_modal = true
} }
} }
} }

View File

@ -1,18 +1,20 @@
<template> <template>
<div class="media is-align-items-center"> <template v-for="item in items" :key="item.id">
<div class="media-content is-clickable is-clipped" @click="open_artist"> <div class="media is-align-items-center">
<h1 class="title is-6" v-text="item.name" /> <div class="media-content is-clickable is-clipped" @click="open(item)">
<h1 class="title is-6" v-text="item.name" />
</div>
<div class="media-right">
<a @click.prevent.stop="open_dialog(item)">
<mdicon class="icon has-text-dark" name="dots-vertical" size="16" />
</a>
</div>
</div> </div>
<div class="media-right"> </template>
<a @click.prevent.stop="show_details_modal = true">
<mdicon class="icon has-text-dark" name="dots-vertical" size="16" />
</a>
</div>
</div>
<teleport to="#app"> <teleport to="#app">
<modal-dialog-artist-spotify <modal-dialog-artist-spotify
:show="show_details_modal" :show="show_details_modal"
:artist="item" :artist="selected_item"
@close="show_details_modal = false" @close="show_details_modal = false"
/> />
</teleport> </teleport>
@ -24,17 +26,21 @@ import ModalDialogArtistSpotify from '@/components/ModalDialogArtistSpotify.vue'
export default { export default {
name: 'ListItemArtistSpotify', name: 'ListItemArtistSpotify',
components: { ModalDialogArtistSpotify }, components: { ModalDialogArtistSpotify },
props: { item: { required: true, type: Object } }, props: { items: { required: true, type: Object } },
data() { data() {
return { show_details_modal: false } return { selected_item: {}, show_details_modal: false }
}, },
methods: { methods: {
open_artist() { open(item) {
this.$router.push({ this.$router.push({
name: 'music-spotify-artist', name: 'music-spotify-artist',
params: { id: this.item.id } params: { id: item.id }
}) })
},
open_dialog(item) {
this.selected_item = item
this.show_details_modal = true
} }
} }
} }

View File

@ -1,19 +1,21 @@
<template> <template>
<div class="media is-align-items-center"> <template v-for="item in items" :key="item.id">
<div class="media-content is-clickable is-clipped" @click="open_playlist"> <div class="media is-align-items-center">
<h1 class="title is-6" v-text="item.name" /> <div class="media-content is-clickable is-clipped" @click="open(item)">
<h2 class="subtitle is-7" v-text="item.owner.display_name" /> <h1 class="title is-6" v-text="item.name" />
<h2 class="subtitle is-7" v-text="item.owner.display_name" />
</div>
<div class="media-right">
<a @click.prevent.stop="open_dialog(item)">
<mdicon class="icon has-text-dark" name="dots-vertical" size="16" />
</a>
</div>
</div> </div>
<div class="media-right"> </template>
<a @click.prevent.stop="show_details_modal = true">
<mdicon class="icon has-text-dark" name="dots-vertical" size="16" />
</a>
</div>
</div>
<teleport to="#app"> <teleport to="#app">
<modal-dialog-playlist-spotify <modal-dialog-playlist-spotify
:show="show_details_modal" :show="show_details_modal"
:playlist="item" :playlist="selected_item"
@close="show_details_modal = false" @close="show_details_modal = false"
/> />
</teleport> </teleport>
@ -27,18 +29,19 @@ export default {
components: { components: {
ModalDialogPlaylistSpotify ModalDialogPlaylistSpotify
}, },
props: { item: { required: true, type: Object } }, props: { items: { required: true, type: Object } },
data() { data() {
return { show_details_modal: false } return { selected_item: {}, show_details_modal: false }
}, },
methods: { methods: {
open_playlist() { open(item) {
this.$router.push({ this.$router.push({ name: 'playlist-spotify', params: { id: item.id } })
name: 'playlist-spotify', },
params: { id: this.item.id } open_dialog(item) {
}) this.selected_item = item
this.show_details_modal = true
} }
} }
} }

View File

@ -1,49 +1,51 @@
<template> <template>
<div class="media is-align-items-center"> <template v-for="item in items" :key="item.id">
<div <div class="media is-align-items-center">
class="media-content is-clipped" <div
:class="{ class="media-content is-clipped"
'is-clickable': item.is_playable,
'fd-is-not-allowed': !item.is_playable
}"
@click="play"
>
<h1
class="title is-6"
:class="{ 'has-text-grey-light': !item.is_playable }"
v-text="item.name"
/>
<h2
class="subtitle is-7 has-text-weight-bold"
:class="{ :class="{
'has-text-grey': item.is_playable, 'is-clickable': item.is_playable,
'has-text-grey-light': !item.is_playable 'fd-is-not-allowed': !item.is_playable
}" }"
v-text="item.artists[0].name" @click="play(item)"
/> >
<h2 class="subtitle is-7 has-text-grey" v-text="item.album.name" /> <h1
<h2 v-if="!item.is_playable" class="subtitle is-7"> class="title is-6"
(<span v-text="$t('list.spotify.not-playable-track')" /> :class="{ 'has-text-grey-light': !item.is_playable }"
<span v-text="item.name"
v-if="item.restrictions && item.restrictions.reason" />
v-text=" <h2
$t('list.spotify.restriction-reason', { class="subtitle is-7 has-text-weight-bold"
reason: item.restrictions.reason :class="{
}) 'has-text-grey': item.is_playable,
" 'has-text-grey-light': !item.is_playable
/>) }"
</h2> v-text="item.artists[0].name"
/>
<h2 class="subtitle is-7 has-text-grey" v-text="item.album.name" />
<h2 v-if="!item.is_playable" class="subtitle is-7">
(<span v-text="$t('list.spotify.not-playable-track')" />
<span
v-if="item.restrictions && item.restrictions.reason"
v-text="
$t('list.spotify.restriction-reason', {
reason: item.restrictions.reason
})
"
/>)
</h2>
</div>
<div class="media-right">
<a @click.prevent.stop="open_dialog(item)">
<mdicon class="icon has-text-dark" name="dots-vertical" size="16" />
</a>
</div>
</div> </div>
<div class="media-right"> </template>
<a @click.prevent.stop="show_details_modal = true">
<mdicon class="icon has-text-dark" name="dots-vertical" size="16" />
</a>
</div>
</div>
<teleport to="#app"> <teleport to="#app">
<modal-dialog-track-spotify <modal-dialog-track-spotify
:show="show_details_modal" :show="show_details_modal"
:track="item" :track="selected_item"
@close="show_details_modal = false" @close="show_details_modal = false"
/> />
</teleport> </teleport>
@ -58,19 +60,22 @@ export default {
components: { ModalDialogTrackSpotify }, components: { ModalDialogTrackSpotify },
props: { props: {
context_uri: { default: '', type: String }, context_uri: { default: '', type: String },
item: { required: true, type: Object }, items: { required: true, type: Object }
position: { default: 0, type: Number }
}, },
data() { data() {
return { show_details_modal: false } return { selected_item: {}, show_details_modal: false }
}, },
methods: { methods: {
play() { open_dialog(item) {
if (this.item.is_playable) { this.selected_item = item
this.show_details_modal = true
},
play(item) {
if (item.is_playable) {
webapi.player_play_uri( webapi.player_play_uri(
this.context_uri || this.item.uri, this.context_uri || item.uri,
false, false,
this.position item.position || 0
) )
} }
} }

View File

@ -39,13 +39,7 @@
$t('page.spotify.album.track-count', { count: album.tracks.total }) $t('page.spotify.album.track-count', { count: album.tracks.total })
" "
/> />
<list-item-track-spotify <list-item-track-spotify :items="tracks" :context_uri="album.uri" />
v-for="(track, index) in tracks"
:key="track.id"
:item="track"
:position="index"
:context_uri="album.uri"
/>
<modal-dialog-album-spotify <modal-dialog-album-spotify
:show="show_details_modal" :show="show_details_modal"
:album="album" :album="album"

View File

@ -23,11 +23,7 @@
class="heading has-text-centered-mobile" class="heading has-text-centered-mobile"
v-text="$t('page.spotify.artist.album-count', { count: total })" v-text="$t('page.spotify.artist.album-count', { count: total })"
/> />
<list-item-album-spotify <list-item-album-spotify :items="albums" />
v-for="album in albums"
:key="album.id"
:item="album"
/>
<VueEternalLoading v-if="offset < total" :load="load_next"> <VueEternalLoading v-if="offset < total" :load="load_next">
<template #loading> <template #loading>
<div class="columns is-centered"> <div class="columns is-centered">

View File

@ -7,11 +7,7 @@
<p class="title is-4" v-text="$t('page.spotify.music.new-releases')" /> <p class="title is-4" v-text="$t('page.spotify.music.new-releases')" />
</template> </template>
<template #content> <template #content>
<list-item-album-spotify <list-item-album-spotify :items="new_releases" />
v-for="album in new_releases"
:key="album.id"
:item="album"
/>
</template> </template>
<template #footer> <template #footer>
<nav class="level"> <nav class="level">
@ -34,11 +30,7 @@
/> />
</template> </template>
<template #content> <template #content>
<list-item-playlist-spotify <list-item-playlist-spotify :items="featured_playlists" />
v-for="playlist in featured_playlists"
:key="playlist.id"
:item="playlist"
/>
</template> </template>
<template #footer> <template #footer>
<nav class="level"> <nav class="level">

View File

@ -9,11 +9,7 @@
/> />
</template> </template>
<template #content> <template #content>
<list-item-playlist-spotify <list-item-playlist-spotify :items="featured_playlists" />
v-for="playlist in featured_playlists"
:key="playlist.id"
:item="playlist"
/>
</template> </template>
</content-with-heading> </content-with-heading>
</div> </div>

View File

@ -6,11 +6,7 @@
<p class="title is-4" v-text="$t('page.spotify.music.new-releases')" /> <p class="title is-4" v-text="$t('page.spotify.music.new-releases')" />
</template> </template>
<template #content> <template #content>
<list-item-album-spotify <list-item-album-spotify :items="new_releases" />
v-for="album in new_releases"
:key="album.id"
:item="album"
/>
</template> </template>
</content-with-heading> </content-with-heading>
</div> </div>

View File

@ -25,13 +25,7 @@
$t('page.spotify.playlist.count', { count: playlist.tracks.total }) $t('page.spotify.playlist.count', { count: playlist.tracks.total })
" "
/> />
<list-item-track-spotify <list-item-track-spotify :items="tracks" :context_uri="playlist.uri" />
v-for="track in tracks"
:key="track.id"
:item="track"
:position="track.position"
:context_uri="playlist.uri"
/>
<VueEternalLoading v-if="offset < total" :load="load_next"> <VueEternalLoading v-if="offset < total" :load="load_next">
<template #loading> <template #loading>
<div class="columns is-centered"> <div class="columns is-centered">

View File

@ -38,12 +38,7 @@
<p class="title is-4" v-text="$t(`page.spotify.search.${type}s`)" /> <p class="title is-4" v-text="$t(`page.spotify.search.${type}s`)" />
</template> </template>
<template #content> <template #content>
<component <component :is="components[type]" :items="results[type].items" />
:is="components[type]"
v-for="item in results[type].items"
:key="item.id"
:item="item"
/>
<VueEternalLoading v-if="query.type === type" :load="search_next"> <VueEternalLoading v-if="query.type === type" :load="search_next">
<template #loading> <template #loading>
<div class="columns is-centered"> <div class="columns is-centered">
@ -62,13 +57,9 @@
class="button is-light is-small is-rounded" class="button is-light is-small is-rounded"
@click="open_search(type)" @click="open_search(type)"
v-text=" v-text="
$t( $t(`page.spotify.search.show-${type}s`, results[type].total, {
`page.spotify.search.show-${type}s`, count: $filters.number(results[type].total)
results[type].total, })
{
count: $filters.number(results[type].total)
}
)
" "
/> />
</p> </p>
@ -206,8 +197,8 @@ export default {
search_next({ loaded }) { search_next({ loaded }) {
const items = this.results[this.query.type] const items = this.results[this.query.type]
this.spotify_search([this.query.type]).then((data) => { this.spotify_search([this.query.type]).then((data) => {
const next = Object.values(data)[0] const [next] = Object.values(data)
items.items = items.items.concat(next.items) items.items.push(...next.items)
items.total = next.total items.total = next.total
this.search_param.offset += next.limit this.search_param.offset += next.limit
loaded(next.items.length, PAGE_SIZE) loaded(next.items.length, PAGE_SIZE)