feat: artist images in library artists grid view
The local artists grid now shows Wikimedia artist photos in the circular avatars. Each visible artist card triggers an async load: GetArtistMBID(name) → GetArtistImageURL(mbid) → cached data URL. Images load progressively — the initial letter placeholder shows immediately, replaced by the photo when it resolves. Results are cached in-memory per session. Artists without MBIDs or without Wikimedia photos keep the initial letter fallback. Uses the same disk-cached artist image pipeline as the explore views — no extra network requests for previously resolved artists.
This commit is contained in:
@@ -16,6 +16,7 @@ import {
|
|||||||
GetAlbumsByArtistByLibrary,
|
GetAlbumsByArtistByLibrary,
|
||||||
GetAlbumTracksByLibrary,
|
GetAlbumTracksByLibrary,
|
||||||
} from '@go/library/Library';
|
} from '@go/library/Library';
|
||||||
|
import { GetArtistImageURL, GetArtistMBID } from '@go/explore/Service';
|
||||||
import { library } from '@go/models';
|
import { library } from '@go/models';
|
||||||
import { LibraryController } from '@store/controllers/library-controller';
|
import { LibraryController } from '@store/controllers/library-controller';
|
||||||
import { SearchController } from '@store/controllers/search-controller';
|
import { SearchController } from '@store/controllers/search-controller';
|
||||||
@@ -176,6 +177,8 @@ export class ArtistsView
|
|||||||
private cachedGridEntries: ArtistEntry[] = [];
|
private cachedGridEntries: ArtistEntry[] = [];
|
||||||
private prevFilterArtists: library.Artist[] = [];
|
private prevFilterArtists: library.Artist[] = [];
|
||||||
private prevFilterTerm = '';
|
private prevFilterTerm = '';
|
||||||
|
private artistImageCache = new Map<string, string>();
|
||||||
|
private artistImageLoading = new Set<string>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recompute the filtered-artists and grid-entries
|
* Recompute the filtered-artists and grid-entries
|
||||||
@@ -294,6 +297,13 @@ export class ArtistsView
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.avatar-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
.avatar-placeholder {
|
.avatar-placeholder {
|
||||||
color: var(
|
color: var(
|
||||||
--yj-text-secondary,
|
--yj-text-secondary,
|
||||||
@@ -975,6 +985,60 @@ export class ArtistsView
|
|||||||
* Helpers
|
* Helpers
|
||||||
* ================================================================ */
|
* ================================================================ */
|
||||||
|
|
||||||
|
private renderArtistAvatar(name: string) {
|
||||||
|
const imageURL = this.artistImageCache.get(name);
|
||||||
|
|
||||||
|
// Kick off async image load if not cached.
|
||||||
|
if (!this.artistImageCache.has(name)) {
|
||||||
|
this.loadArtistImage(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imageURL) {
|
||||||
|
return html`<img
|
||||||
|
class="avatar-image"
|
||||||
|
src="${imageURL}"
|
||||||
|
alt="${name}"
|
||||||
|
/>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`<span class="avatar-placeholder">
|
||||||
|
${this.getArtistInitial(name)}
|
||||||
|
</span>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load artist image for a single artist. Resolves MBID by name,
|
||||||
|
* then fetches the cached image. Sequential to avoid rate limit.
|
||||||
|
*/
|
||||||
|
private loadArtistImage(name: string) {
|
||||||
|
if (this.artistImageCache.has(name) || this.artistImageLoading.has(name)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.artistImageLoading.add(name);
|
||||||
|
|
||||||
|
GetArtistMBID(name)
|
||||||
|
.then((mbid) => {
|
||||||
|
if (!mbid) return Promise.resolve('');
|
||||||
|
|
||||||
|
return GetArtistImageURL(mbid);
|
||||||
|
})
|
||||||
|
.then((url) => {
|
||||||
|
if (url) {
|
||||||
|
this.artistImageCache.set(name, url);
|
||||||
|
this.requestUpdate();
|
||||||
|
} else {
|
||||||
|
this.artistImageCache.set(name, '');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.artistImageCache.set(name, '');
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.artistImageLoading.delete(name);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private getArtistInitial(
|
private getArtistInitial(
|
||||||
name: string,
|
name: string,
|
||||||
): string {
|
): string {
|
||||||
@@ -1047,11 +1111,7 @@ export class ArtistsView
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div class="avatar-container">
|
<div class="avatar-container">
|
||||||
<span class="avatar-placeholder">
|
${this.renderArtistAvatar(artist.Name)}
|
||||||
${this.getArtistInitial(
|
|
||||||
artist.Name,
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="artist-name"
|
class="artist-name"
|
||||||
|
|||||||
Reference in New Issue
Block a user