fix: double click on tracks is now working

+ fix see all link not being shown in favorites and artist page
This commit is contained in:
cwilvx
2025-08-14 21:14:18 +03:00
parent 6f4a59f971
commit cf2d9537ff
5 changed files with 192 additions and 127 deletions

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="statitem" :class="props.icon"> <div class="statitem" :class="props.icon" :style="dynamicBackgroundStyle">
<svg <svg
class="noise" class="noise"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@@ -35,7 +35,7 @@
surfaceScale="21" surfaceScale="21"
specularConstant="1.7" specularConstant="1.7"
specularExponent="20" specularExponent="20"
lighting-color="#7957A8" lighting-color="transparent"
x="0%" x="0%"
y="0%" y="0%"
width="100%" width="100%"
@@ -50,20 +50,20 @@
<rect width="700" height="700" fill="transparent"></rect> <rect width="700" height="700" fill="transparent"></rect>
<rect width="700" height="700" fill="#7957a8" filter="url(#nnnoise-filter)"></rect> <rect width="700" height="700" fill="#7957a8" filter="url(#nnnoise-filter)"></rect>
</svg> </svg>
<div class="itemcontent"> <div class="itemcontent" :style="{ color: textColor }">
<div class="count ellip2" :title="formattedValue">{{ formattedValue }}</div> <div class="count ellip2" :title="formattedValue">{{ formattedValue }}</div>
<div class="title">{{ text }}</div> <div class="title">{{ text }}</div>
</div> </div>
<component :is="icon" class="staticon" v-if="!props.icon.startsWith('top')" /> <component :is="icon" v-if="!props.icon.startsWith('top')" class="staticon" :style="{ color: textColor }" />
<router-link <router-link
v-if="props.icon.startsWith('top') && props.image"
:to="{ :to="{
name: Routes.album, name: Routes.album,
params: { params: {
albumhash: props.image?.replace('.webp', ''), albumhash: props.image?.replace('.webp', ''),
}, },
}" }"
v-if="props.icon.startsWith('top') && props.image"
> >
<img class="staticon statimage shadow-sm" :src="paths.images.thumb.small + props.image" alt="" /> <img class="staticon statimage shadow-sm" :src="paths.images.thumb.small + props.image" alt="" />
</router-link> </router-link>
@@ -81,6 +81,11 @@ import SparklesSvg from '@/assets/icons/sparkles.svg'
import { paths } from '@/config' import { paths } from '@/config'
import { Routes } from '@/router' import { Routes } from '@/router'
import useArtistStore from '@/stores/pages/artist'
import useAlbumStore from '@/stores/pages/album'
import { storeToRefs } from 'pinia'
import { useRoute } from 'vue-router'
import { getTextColor } from '@/utils/colortools/shift'
const props = defineProps<{ const props = defineProps<{
value: string value: string
@@ -89,6 +94,13 @@ const props = defineProps<{
image?: string image?: string
}>() }>()
// Get current route and colors from stores
const route = useRoute()
const artistStore = useArtistStore()
const albumStore = useAlbumStore()
const { colors: artistColors } = storeToRefs(artistStore)
const { colors: albumColors } = storeToRefs(albumStore)
const icon = computed(() => { const icon = computed(() => {
switch (props.icon) { switch (props.icon) {
case 'streams': case 'streams':
@@ -110,6 +122,61 @@ const icon = computed(() => {
const formattedValue = computed(() => { const formattedValue = computed(() => {
return props.value.toLocaleString() return props.value.toLocaleString()
}) })
// Determine which dynamic color to use based on current route
const dynamicColor = computed(() => {
switch (route.name) {
// Album-related pages should use album colors
case Routes.album:
return albumColors.value?.bg || null
// Artist-related pages should use artist colors
case Routes.artist:
return artistColors.value?.bg || null
// All other pages should use default colors
default:
return null
}
})
// Default hardcoded background styles
const defaultBackgroundStyles = computed(() => {
switch (props.icon) {
case 'streams':
return 'linear-gradient(to top, #c79081 0%, #dfa579 100%)'
case 'playtime':
return 'linear-gradient(-225deg, #3d4e81 0%, #5753c9 48%, #6e7ff3 100%)'
case 'trackcount':
return 'linear-gradient(to top, #6a66b9 0%, #7777db 52%, #7b7bd4 100%)'
case 'toptrack':
return 'linear-gradient(-225deg, #65379b 0%, #6750b3 53%, #6457c6 100%)'
default:
return 'linear-gradient(to top right, rgb(120, 76, 129), #9643da91, rgb(132, 80, 228))'
}
})
// Computed style that uses dynamic color or falls back to hardcoded
const dynamicBackgroundStyle = computed(() => {
if (dynamicColor.value) {
return {
backgroundColor: dynamicColor.value,
backgroundImage: 'none',
}
}
return {
backgroundImage: defaultBackgroundStyles.value,
}
})
// Computed text color based on background using the same logic as headers
const textColor = computed(() => {
if (dynamicColor.value) {
return getTextColor(dynamicColor.value)
}
// Return default white color when using gradients
return '#ffffff'
})
</script> </script>
<style lang="scss"> <style lang="scss">
@@ -121,25 +188,10 @@ const formattedValue = computed(() => {
aspect-ratio: 1; aspect-ratio: 1;
overflow: hidden; overflow: hidden;
// Default background - will be overridden by dynamic styles
background-image: linear-gradient(to top right, rgb(120, 76, 129), #9643da91, rgb(132, 80, 228)); background-image: linear-gradient(to top right, rgb(120, 76, 129), #9643da91, rgb(132, 80, 228));
position: relative; position: relative;
&.streams {
background-image: linear-gradient(to top, #c79081 0%, #dfa579 100%);
}
&.playtime {
background-image: linear-gradient(-225deg, #3d4e81 0%, #5753c9 48%, #6e7ff3 100%);
}
&.trackcount {
background-image: linear-gradient(to top, #6a66b9 0%, #7777db 52%, #7b7bd4 100%);
}
&.toptrack {
background-image: linear-gradient(-225deg, #65379b 0%, #6750b3 53%, #6457c6 100%);
}
.itemcontent { .itemcontent {
position: relative; position: relative;
z-index: 1; z-index: 1;

View File

@@ -7,11 +7,18 @@
{{ title }} {{ title }}
</RouterLink> </RouterLink>
</b> </b>
<!-- INFO: This SEE ALL is shown when there's no description. Eg. in favorites page -->
<SeeAll
v-if="!description && route && itemlist.length >= maxAbumCards"
:route="route"
:text="seeAllText"
/>
</div> </div>
<div v-if="description" class="rdesc"> <div v-if="description" class="rdesc">
<RouterLink :to="route || ''"> <RouterLink :to="route || ''">
{{ description }} {{ description }}
</RouterLink> </RouterLink>
<!-- INFO: This SEE ALL is shown when there's a description. Eg. in the home page -->
<SeeAll v-if="route && itemlist.length >= maxAbumCards" :route="route" :text="seeAllText" /> <SeeAll v-if="route && itemlist.length >= maxAbumCards" :route="route" :text="seeAllText" />
</div> </div>
</div> </div>
@@ -163,6 +170,9 @@ function getProps(item: { type: string; item?: any; with_helptext?: boolean }) {
.rtitle { .rtitle {
font-size: 1.15rem; font-size: 1.15rem;
display: flex;
align-items: baseline;
justify-content: space-between;
} }
.rdesc { .rdesc {

View File

@@ -2,14 +2,15 @@
<div <div
class="songlist-item rounded-sm" class="songlist-item rounded-sm"
:class="[{ current: isCurrent() }, { contexton: context_menu_showing }]" :class="[{ current: isCurrent() }, { contexton: context_menu_showing }]"
@dblclick="emitUpdate"
@contextmenu.prevent="showMenu" @contextmenu.prevent="showMenu"
> >
<TrackIndex <TrackIndex
v-if="!isSmall" v-if="!isSmall"
:index="index" :index="index"
:is_fav="is_fav" :is_fav="is_fav"
@add-to-fav="addToFav(track.trackhash)"
:show-inline-fav-icon="settings.showInlineFavIcon" :show-inline-fav-icon="settings.showInlineFavIcon"
@add-to-fav="addToFav(track.trackhash)"
/> />
<TrackTitle <TrackTitle
@@ -29,13 +30,13 @@
/> />
<TrackDuration <TrackDuration
:duration="track.duration || 0" :duration="track.duration || 0"
@showMenu="showMenu"
@toggleFav="addToFav(track.trackhash)"
:help_text="track.help_text" :help_text="track.help_text"
:is_fav="is_fav" :is_fav="is_fav"
:showFavIcon="!isFavoritesPage" :showFavIcon="!isFavoritesPage"
:showInlineFavIcon="settings.showInlineFavIcon" :showInlineFavIcon="settings.showInlineFavIcon"
:highlightFavoriteTracks="settings.highlightFavoriteTracks" :highlightFavoriteTracks="settings.highlightFavoriteTracks"
@showMenu="showMenu"
@toggleFav="addToFav(track.trackhash)"
/> />
</div> </div>
</template> </template>

View File

@@ -10,7 +10,7 @@ import useTracklist from './queue/tracklist'
import useSettings from './settings' import useSettings from './settings'
import useTracker from './tracker' import useTracker from './tracker'
import { paths } from '@/config' import { getBaseUrl, paths } from '@/config'
import updateMediaNotif from '@/helpers/mediaNotification' import updateMediaNotif from '@/helpers/mediaNotification'
import { crossFade } from '@/utils/audio/crossFade' import { crossFade } from '@/utils/audio/crossFade'
@@ -127,9 +127,11 @@ export function getUrl(filepath: string, trackhash: string, use_legacy: boolean)
use_legacy = true use_legacy = true
const { streaming_container, streaming_quality } = useSettings() const { streaming_container, streaming_quality } = useSettings()
return `${paths.api.files}/${trackhash + (use_legacy ? '/legacy' : '')}?filepath=${encodeURIComponent( const url = `${paths.api.files}/${trackhash + (use_legacy ? '/legacy' : '')}?filepath=${encodeURIComponent(
filepath filepath
)}&container=${streaming_container}&quality=${streaming_quality}` )}&container=${streaming_container}&quality=${streaming_quality}`
return getBaseUrl() + url
} }
const audioSource = new AudioSource() const audioSource = new AudioSource()

View File

@@ -1,128 +1,128 @@
<template> <template>
<div class="content-page favorites-page"> <div class="content-page favorites-page">
<GenericHeader> <GenericHeader>
<template #name>Favorites</template> <template #name>Favorites</template>
<template #description <template #description
>{{ count.tracks }} Tracks {{ count.albums }} Albums {{ count.artists }} Artists</template >{{ count.tracks }} Tracks {{ count.albums }} Albums {{ count.artists }} Artists</template
> >
</GenericHeader> </GenericHeader>
<CardScroller <CardScroller
v-if="recentFavs.length" v-if="recentFavs.length"
class="recent-favs" class="recent-favs"
:items="recentFavs" :items="recentFavs"
:title="'Recent'" :title="'Recent'"
:play-source="playSources.favorite" :play-source="playSources.favorite"
/> />
<div v-if="favTracks.length" class="fav-tracks"> <div v-if="favTracks.length" class="fav-tracks">
<TopTracks <TopTracks
:tracks="favTracks" :tracks="favTracks"
:route="'/favorites/tracks'" :route="'/favorites/tracks'"
:title="'Tracks'" :title="'Tracks'"
:play-handler="handlePlay" :play-handler="handlePlay"
:source="dropSources.favorite" :source="dropSources.favorite"
:total="count.tracks" :total="count.tracks"
/> />
</div>
<br />
<CardScroller
v-if="favAlbums.length"
:items="favAlbums.map(i => ({ type: 'album', item: i }))"
:title="'Albums'"
:route="'/favorites/albums'"
/>
<CardScroller
v-if="favArtists.length"
:items="favArtists.map(i => ({ type: 'artist', item: i }))"
:title="'Artists'"
:route="'/favorites/artists'"
/>
<NoItems :flag="noFavs" :icon="HeartSvg" :title="'No favorites found'" :description="description" />
</div> </div>
<br />
<CardScroller
v-if="favAlbums.length"
:items="favAlbums.map((i) => ({ type: 'album', item: i }))"
:title="'Albums'"
:route="'/favorites/albums'"
/>
<CardScroller
v-if="favArtists.length"
:items="favArtists.map((i) => ({ type: 'artist', item: i }))"
:title="'Artists'"
:route="'/favorites/artists'"
/>
<NoItems :flag="noFavs" :icon="HeartSvg" :title="'No favorites found'" :description="description" />
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { nextTick, onMounted, Ref, ref } from "vue"; import { nextTick, onMounted, Ref, ref } from 'vue'
import { maxAbumCards, updateCardWidth } from "@/stores/content-width"; import { maxAbumCards, updateCardWidth } from '@/stores/content-width'
import { dropSources, playSources } from "@/enums"; import { dropSources, playSources } from '@/enums'
import { playFromFavorites } from "@/helpers/usePlayFrom"; import { playFromFavorites } from '@/helpers/usePlayFrom'
import { Album, Artist, RecentFavResult, Track } from "@/interfaces"; import { Album, Artist, RecentFavResult, Track } from '@/interfaces'
import { getAllFavs } from "@/requests/favorite"; import { getAllFavs } from '@/requests/favorite'
import updatePageTitle from "@/utils/updatePageTitle"; import updatePageTitle from '@/utils/updatePageTitle'
import HeartSvg from "@/assets/icons/heart-no-color.svg"; import HeartSvg from '@/assets/icons/heart-no-color.svg'
import TopTracks from "@/components/ArtistView/TopTracks.vue"; import TopTracks from '@/components/ArtistView/TopTracks.vue'
import CardScroller from "@/components/shared/CardScroller.vue"; import CardScroller from '@/components/shared/CardScroller.vue'
import GenericHeader from "@/components/shared/GenericHeader.vue"; import GenericHeader from '@/components/shared/GenericHeader.vue'
import NoItems from "@/components/shared/NoItems.vue"; import NoItems from '@/components/shared/NoItems.vue'
const description = `You can add tracks, albums and artists to your favorites by clicking the heart icon`; const description = `You can add tracks, albums and artists to your favorites by clicking the heart icon`
const recentFavs: Ref<RecentFavResult[]> = ref([]); const recentFavs: Ref<RecentFavResult[]> = ref([])
const favAlbums: Ref<Album[]> = ref([]); const favAlbums: Ref<Album[]> = ref([])
const favTracks: Ref<Track[]> = ref([]); const favTracks: Ref<Track[]> = ref([])
const favArtists: Ref<Artist[]> = ref([]); const favArtists: Ref<Artist[]> = ref([])
const count = ref({ const count = ref({
albums: 0, albums: 0,
tracks: 0, tracks: 0,
artists: 0, artists: 0,
}); })
const noFavs = ref(false); const noFavs = ref(false)
onMounted(() => { onMounted(() => {
updatePageTitle("Favorites"); updatePageTitle('Favorites')
const max = maxAbumCards.value; const max = maxAbumCards.value
getAllFavs(6, max, max) getAllFavs(6, max, max)
.then((favs) => { .then(favs => {
recentFavs.value = favs.recents; recentFavs.value = favs.recents
favAlbums.value = favs.albums; favAlbums.value = favs.albums
favTracks.value = favs.tracks; favTracks.value = favs.tracks
favArtists.value = favs.artists; favArtists.value = favs.artists
count.value = favs.count; count.value = favs.count
}) })
.then(() => { .then(() => {
noFavs.value = !favAlbums.value.length && !favTracks.value.length && !favArtists.value.length; noFavs.value = !favAlbums.value.length && !favTracks.value.length && !favArtists.value.length
}) })
.then(async () => { .then(async () => {
await nextTick(); await nextTick()
updateCardWidth(); updateCardWidth()
}); })
}); })
function handlePlay(index: number) { function handlePlay(index: number) {
const track = favTracks.value[index]; const track = favTracks.value[index]
if (!track) return; if (!track) return
playFromFavorites(track); playFromFavorites(track)
} }
</script> </script>
<style lang="scss"> <style lang="scss">
.favorites-page { .favorites-page {
height: 100%; height: 100%;
overflow: auto; overflow: auto;
.recent-favs { .recent-favs {
padding-top: 1rem; padding-top: 1rem;
}
.nothing h3 {
margin-top: 3rem;
}
.fav-tracks {
h3 {
margin-top: 0;
} }
.artist-top-tracks { .nothing h3 {
h3 { margin-top: 3rem;
padding-right: $small; }
}
.fav-tracks {
h3 {
margin-top: 0;
}
.artist-top-tracks {
h3 {
padding-right: $small;
}
}
} }
}
} }
</style> </style>