mirror of
https://github.com/swingmx/webclient.git
synced 2025-12-23 19:00:21 +00:00
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:
@@ -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;
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user