This commit is contained in:
Generated
+1
-1
@@ -1418,7 +1418,7 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "furumusic"
|
name = "furumusic"
|
||||||
version = "0.4.3"
|
version = "0.4.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "furumusic"
|
name = "furumusic"
|
||||||
version = "0.4.4"
|
version = "0.4.5"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Reusable web-app boilerplate: auth, OIDC/SSO, admin panel, user management, i18n, PostgreSQL"
|
description = "Reusable web-app boilerplate: auth, OIDC/SSO, admin panel, user management, i18n, PostgreSQL"
|
||||||
|
|
||||||
|
|||||||
@@ -373,6 +373,7 @@ translations! {
|
|||||||
player_repeat: "Repeat" , "Повтор";
|
player_repeat: "Repeat" , "Повтор";
|
||||||
player_volume: "Volume" , "Громкость";
|
player_volume: "Volume" , "Громкость";
|
||||||
player_appears_on: "Appears on" , "Участвует в";
|
player_appears_on: "Appears on" , "Участвует в";
|
||||||
|
player_top_tracks: "Popular tracks" , "Популярные треки";
|
||||||
player_albums: "Albums" , "Альбомы";
|
player_albums: "Albums" , "Альбомы";
|
||||||
player_eps: "EPs" , "EP";
|
player_eps: "EPs" , "EP";
|
||||||
player_singles: "Singles" , "Синглы";
|
player_singles: "Singles" , "Синглы";
|
||||||
|
|||||||
+38
-34
@@ -3201,40 +3201,44 @@ async fn artist_detail_handler(
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let top_tracks = sqlx::query_as::<_, PlaylistTrackRow>(
|
let top_tracks = sqlx::query_as::<_, PlaylistTrackRow>(
|
||||||
r#"SELECT t.id, t.title::text as title, t.track_number, t.disc_number,
|
r#"SELECT * FROM (
|
||||||
t.duration_seconds, t.cover_file_id,
|
SELECT DISTINCT ON (lower(t.title::text))
|
||||||
r.cover_file_id as release_cover_file_id,
|
t.id, t.title::text as title, t.track_number, t.disc_number,
|
||||||
r.id as release_id,
|
t.duration_seconds, t.cover_file_id,
|
||||||
r.title::text as release_title,
|
r.cover_file_id as release_cover_file_id,
|
||||||
r.year as release_year,
|
r.id as release_id,
|
||||||
COALESCE(mf.uploader_name, 'UFO')::text AS uploader_name,
|
r.title::text as release_title,
|
||||||
mf.audio_format,
|
r.year as release_year,
|
||||||
mf.audio_bitrate,
|
COALESCE(mf.uploader_name, 'UFO')::text AS uploader_name,
|
||||||
mf.audio_sample_rate,
|
mf.audio_format,
|
||||||
mf.audio_bit_depth,
|
mf.audio_bitrate,
|
||||||
mf.file_size_bytes,
|
mf.audio_sample_rate,
|
||||||
t.lastfm_listeners,
|
mf.audio_bit_depth,
|
||||||
t.lastfm_playcount,
|
mf.file_size_bytes,
|
||||||
t.lastfm_rating,
|
t.lastfm_listeners,
|
||||||
t.lastfm_updated_at
|
t.lastfm_playcount,
|
||||||
FROM furumusic__track t
|
t.lastfm_rating,
|
||||||
JOIN furumusic__release r ON r.id = t.release_id
|
t.lastfm_updated_at
|
||||||
LEFT JOIN furumusic__media_file mf ON mf.id = t.audio_file_id
|
FROM furumusic__track t
|
||||||
WHERE t.is_hidden = false
|
JOIN furumusic__release r ON r.id = t.release_id
|
||||||
AND r.is_hidden = false
|
LEFT JOIN furumusic__media_file mf ON mf.id = t.audio_file_id
|
||||||
AND EXISTS (
|
WHERE t.is_hidden = false
|
||||||
SELECT 1
|
AND r.is_hidden = false
|
||||||
FROM furumusic__track_artist ta
|
AND EXISTS (
|
||||||
WHERE ta.track_id = t.id
|
SELECT 1
|
||||||
AND ta.artist_id = $1
|
FROM furumusic__track_artist ta
|
||||||
AND ta.role <> 'featuring'
|
WHERE ta.track_id = t.id
|
||||||
)
|
AND ta.artist_id = $1
|
||||||
ORDER BY COALESCE(t.lastfm_rating, 0) DESC,
|
AND ta.role <> 'featuring'
|
||||||
COALESCE(t.lastfm_playcount, 0) DESC,
|
)
|
||||||
COALESCE(t.lastfm_listeners, 0) DESC,
|
ORDER BY lower(t.title::text), COALESCE(t.lastfm_rating, 0) DESC
|
||||||
r.year DESC NULLS LAST,
|
) deduped
|
||||||
t.track_number NULLS LAST,
|
ORDER BY COALESCE(lastfm_rating, 0) DESC,
|
||||||
t.id
|
COALESCE(lastfm_playcount, 0) DESC,
|
||||||
|
COALESCE(lastfm_listeners, 0) DESC,
|
||||||
|
release_year DESC NULLS LAST,
|
||||||
|
track_number NULLS LAST,
|
||||||
|
id
|
||||||
LIMIT 50"#,
|
LIMIT 50"#,
|
||||||
)
|
)
|
||||||
.bind(artist_id)
|
.bind(artist_id)
|
||||||
|
|||||||
@@ -620,6 +620,83 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<template x-if="$store.library.currentArtist.top_tracks && $store.library.currentArtist.top_tracks.length > 0">
|
||||||
|
<section class="artist-release-group">
|
||||||
|
<h2 class="artist-release-group-title">{{ t.player_top_tracks }}</h2>
|
||||||
|
<div class="track-list-header">
|
||||||
|
<span>#</span>
|
||||||
|
<span>{{ t.player_title }}</span>
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
<span style="text-align:right">{{ t.player_duration }}</span>
|
||||||
|
</div>
|
||||||
|
<template x-for="(track, idx) in $store.library.currentArtist.top_tracks" :key="track.id">
|
||||||
|
<div class="track-row artist-appearance-row"
|
||||||
|
:class="{ playing: $store.player.currentTrack && $store.player.currentTrack.id === track.id }"
|
||||||
|
@dblclick="$store.queue.playRelease($store.library.currentArtist.top_tracks, idx)">
|
||||||
|
<span class="track-num" x-text="idx + 1"></span>
|
||||||
|
<div class="track-info">
|
||||||
|
<button class="artist-appearance-cover"
|
||||||
|
type="button"
|
||||||
|
@click.stop="$store.library.openRelease(track.release_id)"
|
||||||
|
:title="track.release_title"
|
||||||
|
aria-label="{{ t.player_release }}">
|
||||||
|
<template x-if="track.cover_url">
|
||||||
|
<img :src="track.cover_url" :alt="track.release_title" loading="lazy">
|
||||||
|
</template>
|
||||||
|
<template x-if="!track.cover_url">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="12" cy="12" r="4"/></svg>
|
||||||
|
</template>
|
||||||
|
</button>
|
||||||
|
<div class="artist-appearance-copy">
|
||||||
|
<div class="track-title">
|
||||||
|
<span x-text="track.title"></span>
|
||||||
|
<span style="color:var(--text-subdued)"> · </span>
|
||||||
|
<a class="artist-link" @click.stop="$store.library.openRelease(track.release_id)" x-text="track.release_title"></a>
|
||||||
|
</div>
|
||||||
|
<div class="track-artists-inline">
|
||||||
|
<template x-for="(artist, artistIdx) in $store.library.trackArtistLinks(track)" :key="artist.label + '-' + artist.id + '-' + artistIdx">
|
||||||
|
<span>
|
||||||
|
<template x-if="artistIdx > 0"><span>, </span></template>
|
||||||
|
<a class="artist-link" @click.stop="$store.library.openArtist(artist.id)" x-text="artist.label"></a>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span></span>
|
||||||
|
<div class="track-actions">
|
||||||
|
<button class="track-action-btn info-btn popularity-info-btn"
|
||||||
|
:class="{ 'has-popularity': $store.library.hasPopularity(track), 'no-popularity': !$store.library.hasPopularity(track) }"
|
||||||
|
:style="$store.library.popularityStyle(track)"
|
||||||
|
@click.stop="$store.library.openTrackInfo(track)"
|
||||||
|
:title="$store.library.trackInfoTitle(track)"
|
||||||
|
aria-label="{{ t.player_track_info }}">
|
||||||
|
<span x-show="$store.library.hasPopularity(track)" x-text="$store.library.popularityLabel(track)"></span>
|
||||||
|
<span x-show="!$store.library.hasPopularity(track)" class="info-letter">i</span>
|
||||||
|
</button>
|
||||||
|
<button class="like-btn" :class="{ liked: $store.likes.has(track.id) }" @click.stop="$store.likes.toggle(track.id)" title="{{ t.player_like }}">
|
||||||
|
<svg viewBox="0 0 24 24" :fill="$store.likes.has(track.id) ? 'currentColor' : 'none'" stroke="currentColor" stroke-width="2"><path d="M20.84 4.61a5.5 5.5 0 00-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 00-7.78 7.78L12 21.23l8.84-8.84a5.5 5.5 0 000-7.78z"/></svg>
|
||||||
|
</button>
|
||||||
|
<button class="track-action-btn queue-insert-btn queue-next-btn" @click.stop="$store.queue.addNextInQueue([track])" title="{{ t.player_play_next }}">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 6h10M5 12h7M5 18h10"/><path d="M17 9l4 3-4 3" fill="currentColor" stroke="none"/></svg>
|
||||||
|
</button>
|
||||||
|
<button class="track-action-btn queue-insert-btn queue-end-btn" @click.stop="$store.queue.addToEnd([track])" title="{{ t.player_add_to_queue }}">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 6h14M5 12h14M5 18h7"/><path d="M17 15l4 3-4 3" fill="currentColor" stroke="none"/></svg>
|
||||||
|
</button>
|
||||||
|
<button class="track-action-btn track-share-btn" @click.stop="$store.sharing.copyTrack(track, $event.currentTarget)" title="{{ t.player_share_track }}" aria-label="{{ t.player_share_track }}">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><path d="M8.6 10.6l6.8-3.9M8.6 13.4l6.8 3.9"/></svg>
|
||||||
|
</button>
|
||||||
|
<button class="track-action-btn playlist-add-btn" @click.stop="$store.playlists.showPicker([track.id])" title="{{ t.player_add_to_playlist }}">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01"/></svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<span class="track-duration" x-text="formatTime(track.duration_seconds)"></span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
<template x-for="group in $store.library.artistReleaseGroups()" :key="group.type">
|
<template x-for="group in $store.library.artistReleaseGroups()" :key="group.type">
|
||||||
<section class="artist-release-group">
|
<section class="artist-release-group">
|
||||||
<h2 class="artist-release-group-title" x-text="group.label"></h2>
|
<h2 class="artist-release-group-title" x-text="group.label"></h2>
|
||||||
|
|||||||
Reference in New Issue
Block a user