fix(front): Correct fallback cover images for activity list in user profile

This commit is contained in:
ArneBo 2025-01-20 14:02:41 +01:00
parent 9af9dc4a3f
commit fc10fca736
2 changed files with 65 additions and 35 deletions

View File

@ -1,10 +1,11 @@
<script setup lang="ts"> <script setup lang="ts">
import type { Track, Listening } from '~/types' import type { Track, Listening } from '~/types'
import { ref, reactive, watch } from 'vue' import { ref, reactive, watch, computed } from 'vue'
import { useStore } from '~/store' import { useStore } from '~/store'
import { clone } from 'lodash-es' import { clone } from 'lodash-es'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { getArtistCoverUrl } from '~/utils/utils'
import axios from 'axios' import axios from 'axios'
@ -14,10 +15,10 @@ import PlayButton from '~/components/audio/PlayButton.vue'
import TagsList from '~/components/tags/List.vue' import TagsList from '~/components/tags/List.vue'
import Alert from '~/components/ui/Alert.vue' import Alert from '~/components/ui/Alert.vue'
import Spacer from '~/components/ui/Spacer.vue' import Spacer from '~/components/ui/Spacer.vue'
import Loader from '~/components/ui/Loader.vue'
import useErrorHandler from '~/composables/useErrorHandler' import useErrorHandler from '~/composables/useErrorHandler'
import { getArtistCoverUrl } from '~/utils/utils'
interface Events { interface Events {
(e: 'count', count: number): void (e: 'count', count: number): void
@ -84,14 +85,48 @@ watch(
watch(count, (to) => emit('count', to)) watch(count, (to) => emit('count', to))
watch(() => props.websocketHandlers.includes('Listen'), (to) => { watch(() => props.websocketHandlers.includes('Listen'), (to) => {
if (to) {
useWebSocketHandler('Listen', (event) => { useWebSocketHandler('Listen', (event) => {
// TODO (wvffle): Add reactivity to recently listened / favorited / added (#1316, #1534) // Handle WebSocket events for "Listen"
// count.value += 1
// objects.unshift(event as Listening) // Add the event to `objects` reactively
// objects.pop() objects.unshift(event as Listening);
})
}) // Keep the array size within limits (e.g., remove the last item if needed)
if (objects.length > props.limit) {
objects.pop();
}
// Recompute coverUrl for the updated `objects`
console.log('WebSocket event received:', event);
console.log('Updated cover URL:', coverUrl.value);
});
}
});
watch(
objects,
(newObjects) => {
// Process each object to attach a `coverUrl`
newObjects.forEach((object) => {
const track = object.track;
if (!("coverUrl" in object)) {
object.coverUrl = undefined; // Initial value for clarity
}
if (track?.album?.cover) {
object.coverUrl = store.getters["instance/absoluteUrl"](track.album.cover.urls.medium_square_crop);
} else if (track?.cover) {
object.coverUrl = store.getters["instance/absoluteUrl"](track.cover.urls.medium_square_crop);
} else if (track?.artist_credit?.length > 0) {
object.coverUrl = getArtistCoverUrl(track.artist_credit) || false;
} else {
object.coverUrl = false; // Fallback if no cover is found
}
});
},
{ deep: true }
);
</script> </script>
<template> <template>
@ -121,25 +156,14 @@ watch(() => props.websocketHandlers.includes('Listen'), (to) => {
> >
<div class="activity-image"> <div class="activity-image">
<img <img
v-if="object.track.album && object.track.album.cover" v-if="object.coverUrl"
v-lazy="store.getters['instance/absoluteUrl'](object.track.album.cover.urls.medium_square_crop)" v-lazy="object.coverUrl"
alt="" alt="Cover"
> />
<img <i
v-else-if="object.track.cover"
v-lazy="store.getters['instance/absoluteUrl'](object.track.cover.urls.medium_square_crop)"
alt=""
>
<img
v-else-if="object.track.artist_credit && object.track.artist_credit.length > 0"
v-lazy="getArtistCoverUrl(object.track.artist_credit)"
alt=""
>
<img
v-else v-else
alt="" class="bi bi-vinyl-fill"
src="../../../../public/embed-default-cover.jpeg" />
>
<PlayButton <PlayButton
class="play-overlay" class="play-overlay"
:icon-only="true" :icon-only="true"
@ -250,6 +274,10 @@ watch(() => props.websocketHandlers.includes('Listen'), (to) => {
object-fit: cover; object-fit: cover;
} }
> i {
font-size: 36px;
}
> .play-button { > .play-button {
position: absolute; position: absolute;
top: 0; top: 0;

View File

@ -85,6 +85,7 @@ const fetchData = async () => {
} }
watch(props, fetchData, { immediate: true }) watch(props, fetchData, { immediate: true })
const recentActivity = ref(0)
</script> </script>
<template> <template>
@ -140,12 +141,12 @@ watch(props, fetchData, { immediate: true })
<h2 class="ui with-actions header"> <h2 class="ui with-actions header">
{{ t('views.auth.ProfileOverview.header.libraries') }} {{ t('views.auth.ProfileOverview.header.libraries') }}
<div <div
v-if="store.state.auth.authenticated && object.full_username === store.state.auth.fullUsername" v-if="store.state.auth.authenticated && object?.full_username === store.state.auth.fullUsername"
class="actions" class="actions"
> >
</div> </div>
</h2> </h2>
<library-widget :url="`federation/actors/${object.full_username}/libraries/`"> <library-widget :url="`federation/actors/${object?.full_username}/libraries/`">
<template #title> <template #title>
{{ t('views.auth.ProfileOverview.header.sharedLibraries') }} {{ t('views.auth.ProfileOverview.header.sharedLibraries') }}
</template> </template>
@ -156,7 +157,7 @@ watch(props, fetchData, { immediate: true })
<h2 class="ui with-actions header"> <h2 class="ui with-actions header">
{{ t('views.auth.ProfileOverview.header.channels') }} {{ t('views.auth.ProfileOverview.header.channels') }}
<div <div
v-if="store.state.auth.authenticated && object.full_username === store.state.auth.fullUsername" v-if="store.state.auth.authenticated && object?.full_username === store.state.auth.fullUsername"
class="actions" class="actions"
> >
<a <a
@ -168,15 +169,16 @@ watch(props, fetchData, { immediate: true })
</a> </a>
</div> </div>
</h2> </h2>
<channels-widget :filters="{scope: `actor:${object.full_username}`}" /> <channels-widget :filters="{scope: `actor:${object?.full_username}`}" />
</Tab> </Tab>
<Tab :title="t('views.auth.ProfileBase.link.activity')" :to="{name: 'profile.activity', params: routerParams}"> <Tab :title="t('views.auth.ProfileBase.link.activity')" :to="{name: 'profile.activity', params: routerParams}">
<track-widget <track-widget
:url="'history/listenings/'" :url="'history/listenings/'"
:filters="{ scope, ordering: '-creation_date', ...qualityFilters}" :filters="{ scope: `actor:${object.full_username}`, ordering: '-creation_date', ...qualityFilters}"
:websocket-handlers="['Listen']" :websocket-handlers="['Listen']"
@count="recentActivity = $event"
> >
<template #title> <template #title>
{{ t('components.library.Home.header.recentlyListened') }} {{ t('components.library.Home.header.recentlyListened') }}
@ -185,14 +187,14 @@ watch(props, fetchData, { immediate: true })
<track-widget <track-widget
:url="'favorites/tracks/'" :url="'favorites/tracks/'"
:filters="{scope: scope, ordering: '-creation_date'}" :filters="{scope: 'actor:${object.full_username}', ordering: '-creation_date'}"
> >
<template #title> <template #title>
{{ t('components.library.Home.header.recentlyFavorited') }} {{ t('components.library.Home.header.recentlyFavorited') }}
</template> </template>
</track-widget> </track-widget>
<album-widget :filters="{scope: scope, playable: true, ordering: '-creation_date', ...qualityFilters}"> <album-widget :filters="{scope: `actor:${object.full_username}`, playable: true, ordering: '-creation_date', ...qualityFilters}">
<template #title> <template #title>
{{ t('components.library.Home.header.recentlyAdded') }} {{ t('components.library.Home.header.recentlyAdded') }}
</template> </template>