Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
2846c41
Show placeholder as artist image
FranzDeschler Apr 27, 2026
4116a25
Provide setting for downloading and displaying artist images
FranzDeschler Apr 27, 2026
a05ab60
Fix displaying artist images
FranzDeschler Apr 29, 2026
b9ecf5c
Add database migration for ArtistArtwork
FranzDeschler Apr 29, 2026
c718e6b
Access ArtistArtwork via ArtistArtworkRepository
FranzDeschler Apr 29, 2026
f9c9853
Create ArrayUtils for common array functions
FranzDeschler May 1, 2026
a9b7eb8
Provide list of album and track artists via file metadata
FranzDeschler May 1, 2026
92f8e0c
Rename constants "cachedCoverArt*" to "cachedArtwork*" to have a more…
FranzDeschler May 1, 2026
ac878b8
Create CacheService to access cached artist artwork
FranzDeschler May 1, 2026
a67e5c4
Getter for downloading artist artwork
FranzDeschler May 1, 2026
bc7c9a4
Index artist artwork
FranzDeschler May 1, 2026
1225032
Generate artistKey
FranzDeschler May 1, 2026
f1eeb2b
Load artist image from cache via application paths
FranzDeschler May 1, 2026
d1d0d9a
Fix tests
FranzDeschler May 1, 2026
84cdab1
Update translations
FranzDeschler May 1, 2026
cccd1b8
Fix tests
FranzDeschler May 1, 2026
bfea628
Call ArtistArtworkIndexer in IndexingService
FranzDeschler May 1, 2026
67b36be
Update tests
FranzDeschler May 3, 2026
2910270
Update migration
FranzDeschler May 3, 2026
567f9ac
Update repositories
FranzDeschler May 3, 2026
d69e6d8
Add artworkId to ArtistModel
FranzDeschler May 3, 2026
58f53c2
Load artworkId in ArtistService
FranzDeschler May 3, 2026
093ed50
Update artwork indexing
FranzDeschler May 3, 2026
03f4a95
Update tests
FranzDeschler May 3, 2026
60a5a48
Fix application setup
FranzDeschler May 3, 2026
c19bc82
Reuse OnlineArtistImageGetter
FranzDeschler May 3, 2026
44ae880
Fix reference error
FranzDeschler May 3, 2026
8e45763
Improve logging
FranzDeschler May 3, 2026
17aa654
Fix sql queries
FranzDeschler May 3, 2026
3ee7c8f
Fix sql queries
FranzDeschler May 3, 2026
5073889
Handle multiple artists per track
FranzDeschler May 4, 2026
093634e
Generator for ArtistsKey
FranzDeschler May 6, 2026
79f27f0
Use ArtistsKey for merging ArtistsArtwork and Track
FranzDeschler May 6, 2026
f2e0b50
Disable NeedsArtistArtworkIndexing flag for unknown artists
FranzDeschler May 6, 2026
9dd6601
Get rid of NeedsArtistArtworkIndexing flag
FranzDeschler May 8, 2026
426122c
Add option to refresh artists artwork
FranzDeschler May 8, 2026
e66d37a
Fixed tests
FranzDeschler May 9, 2026
7d58324
Fix margins
FranzDeschler May 9, 2026
17fbf54
Add database tests
FranzDeschler May 13, 2026
78a0864
Create custom script for executing database tests in the electron env…
FranzDeschler May 13, 2026
8b21588
Merge branch 'master' into feature/artist-images
FranzDeschler May 13, 2026
b919ab8
Fix unit tests
FranzDeschler May 13, 2026
813b83c
Revert "Provide list of album and track artists via file metadata"
FranzDeschler May 14, 2026
1734b0f
Dynamically set item size of artist components
FranzDeschler May 14, 2026
40bb661
Remove unnecessary delimiter from ArtistArtwork table
FranzDeschler May 14, 2026
051bd83
Fix UI
FranzDeschler May 14, 2026
3d0f418
Remove unused code
FranzDeschler May 14, 2026
03186fd
Undo code changes
FranzDeschler May 14, 2026
f734f4a
Fix logging
FranzDeschler May 14, 2026
cc87d60
Use MusicBrainz API as fallback if last.fm cannot provide artist info…
FranzDeschler May 14, 2026
d3108de
Fix typographic issues
FranzDeschler May 14, 2026
1f0b058
Show artist image as background
FranzDeschler May 17, 2026
2cdc225
Rename migration due to merge conflicts
FranzDeschler May 31, 2026
64ab87b
Merge branch 'master' into feature/artist-images
FranzDeschler May 31, 2026
a8e5a1f
Show artist image as background
FranzDeschler May 17, 2026
eb05f69
Merge remote-tracking branch 'origin/feature/artist-image-background'…
FranzDeschler May 31, 2026
4704dd1
Merge branch 'master' into feature/artist-image-background
FranzDeschler Jul 4, 2026
8667482
small refactoring
FranzDeschler Jul 4, 2026
093ec17
Add unit tests
FranzDeschler Jul 4, 2026
cf779f4
Show artist background image only in "Artists" tab
FranzDeschler Jul 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
<div class="app-selected-artist__backgroundimage"
*ngIf="showArtistBackground"
[ngStyle]="{ 'background-image': 'url(' + artistBackground + ')' }"></div>

<div class="h-100 w-100 d-flex flex-column" #albumBrowserElement>
<div class="h-24px align-items-center justify-content-between d-flex flex-row m-1">
<div class="d-flex flex-row align-items-center">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.app-selected-artist__backgroundimage {
filter: blur(8px);
opacity: 0.15;
background-position: center;
background-repeat: no-repeat;
background-size: cover;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { SettingsMock } from '../../../../testing/settings-mock';
import { TrackServiceBase } from '../../../../services/track/track.service.base';
import { TrackModels } from '../../../../services/track/track-models';
import { DialogServiceBase } from '../../../../services/dialog/dialog.service.base';
import { ArtistModel } from '../../../../services/artist/artist-model';

describe('AlbumBrowserComponent', () => {
let trackServiceMock: IMock<TrackServiceBase>;
Expand Down Expand Up @@ -833,4 +834,49 @@ describe('AlbumBrowserComponent', () => {
playbackServiceMock.verify((x) => x.enqueueAndPlayTracksAsync(tracks.tracks), Times.once());
});
});

describe('selectedArtists', () => {
it('should set the artist image as background when one artist is selected', () => {
// Arrange
const artworkId: string = 'artwork-1';
const artists: ArtistModel[] = [new ArtistModel('Metallica', artworkId, translatorServiceMock.object, applicationPathsMock.object)];

applicationPathsMock.setup((x) => x.artistArtFullPath(artworkId)).returns(() => `Cache/ArtistArt/${artworkId}.jpg`);

const component: AlbumBrowserComponent = createComponent();
expect(component.artistBackground).toEqual('');

// Act
component.selectedArtists = artists;

// Assert
expect(component.artistBackground).toEqual(`file:///Cache/ArtistArt/${artworkId}.jpg`);
});

it('should clear the background when multiple artists are selected', () => {
// Arrange
const artists: ArtistModel[] = [
new ArtistModel('Apocalyptica', 'artwork-1', translatorServiceMock.object, applicationPathsMock.object),
new ArtistModel('Metallica', 'artwork-2', translatorServiceMock.object, applicationPathsMock.object),
];
const component: AlbumBrowserComponent = createComponent();

// Act
component.selectedArtists = artists;

// Assert
expect(component.artistBackground).toEqual('');
});

it('should clear the background when no artist is selected', () => {
// Arrange
const component: AlbumBrowserComponent = createComponent();

// Act
component.selectedArtists = [];

// Assert
expect(component.artistBackground).toEqual('');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { PlaybackService } from '../../../../services/playback/playback.service'
import { SettingsBase } from '../../../../common/settings/settings.base';
import { TrackModels } from '../../../../services/track/track-models';
import { TrackServiceBase } from '../../../../services/track/track.service.base';
import {ArtistModel} from "../../../../services/artist/artist-model";
import { DialogServiceBase } from '../../../../services/dialog/dialog.service.base';

@Component({
Expand Down Expand Up @@ -52,8 +53,9 @@ export class AlbumBrowserComponent implements OnInit, AfterViewInit, OnChanges,
public readonly albumOrderKey = albumOrderKey;

public albumOrderEnum: typeof AlbumOrder = AlbumOrder;

public useCompactYearView: boolean = false;
public artistBackground: string = '';
public showArtistBackground: boolean = false;

public ngOnDestroy(): void {
this.destroy$.next();
Expand Down Expand Up @@ -103,6 +105,22 @@ export class AlbumBrowserComponent implements OnInit, AfterViewInit, OnChanges,
this.mouseSelectionWatcher.initialize(this.albums, false);
}

@Input()
public set selectedArtists(selectedArtists: ArtistModel[]) {
this.artistBackground = '';
if (selectedArtists.length == 1) {
const artworkPath: string = selectedArtists[0].artworkPath;
if (artworkPath !== Constants.emptyImage) {
this.artistBackground = artworkPath.replace(/\\/g, '/');
}
}
}

@Input()
public set showArtistBackgroundImage(showArtistBackground: boolean) {
this.showArtistBackground = this.settings.showArtistImages && showArtistBackground;
}

public get isSortedByYear(): boolean {
return this.selectedAlbumOrder === AlbumOrder.byYearAscending || this.selectedAlbumOrder === AlbumOrder.byYearDescending;
}
Expand Down Expand Up @@ -227,9 +245,9 @@ export class AlbumBrowserComponent implements OnInit, AfterViewInit, OnChanges,
return;
}

const albumKeys = this.albums.map(album => album.albumKey);
const albumKeys = this.albums.map((album) => album.albumKey);
const tracks: TrackModels = this.trackService.getTracksForAlbums(albumKeys);

this.playbackService.forceShuffled();
await this.playbackService.enqueueAndPlayTracksAsync(tracks.tracks);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
><app-album-browser
class="p-3 fill"
[albums]="this.albums | albumsFilter: this.searchService.delayedSearchText"
[showArtistBackgroundImage]="true"
[selectedArtists]="this.selectedArtists"
[(albumsPersister)]="this.albumsPersister"
></app-album-browser
></as-split-area>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
as-split-area {
position: relative;
}
Original file line number Diff line number Diff line change
Expand Up @@ -991,5 +991,49 @@ describe('CollectionArtistsComponent', () => {
expect(component.tracks.tracks[0]).toEqual(track1);
expect(component.tracks.tracks[1]).toEqual(track2);
});

it('should keep the selected artists if the selected artists have changed', async () => {
// Arrange
const artist1: ArtistModel = createArtistModel('artist1');
const artist2: ArtistModel = createArtistModel('artist2');
artistServiceMock.setup((x) => x.getArtists(ArtistType.allArtists)).returns(() => [artist1, artist2]);
artistsPersisterMock.setup((x) => x.getSelectedArtistType()).returns(() => ArtistType.allArtists);

const component: CollectionArtistsComponent = createComponent();
await component.ngOnInit();
expect(component.selectedArtists.length).toEqual(0);

// Act
selectedArtistsChangedMock.next([artist1.name, artist2.name]);

// Assert
expect(component.selectedArtists.length).toEqual(2);
expect(component.selectedArtists[0]).toEqual(artist1);
expect(component.selectedArtists[1]).toEqual(artist2);
});
});

describe('ngOnDestroy', () => {
it('should reset the selected artists', async () => {
// Arrange
const artist1: ArtistModel = createArtistModel('artist1');
const artist2: ArtistModel = createArtistModel('artist2');
artistServiceMock.setup((x) => x.getArtists(ArtistType.allArtists)).returns(() => [artist1, artist2]);
artistsPersisterMock.setup((x) => x.getSelectedArtistType()).returns(() => ArtistType.allArtists);
artistsPersisterMock.setup((x) => x.getSelectedArtists([artist1, artist2])).returns(() => [artist1, artist2]);

const component: CollectionArtistsComponent = createComponent();
await component.ngOnInit();

expect(component.selectedArtists.length).toEqual(2);
expect(component.selectedArtists[0]).toEqual(artist1);
expect(component.selectedArtists[1]).toEqual(artist2);

// Act
component.ngOnDestroy();

// Assert
expect(component.selectedArtists.length).toEqual(0);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,12 @@ export class CollectionArtistsComponent implements OnInit, OnDestroy {
public artists: ArtistModel[] = [];
public albums: AlbumModel[] = [];
public tracks: TrackModels = new TrackModels();
public selectedArtists: ArtistModel[] = [];

public get selectedAlbumOrder(): AlbumOrder {
return this._selectedAlbumOrder;
}

public set selectedAlbumOrder(v: AlbumOrder) {
this._selectedAlbumOrder = v;
this.albumsPersister.setSelectedAlbumOrder(v);
Expand All @@ -78,6 +80,7 @@ export class CollectionArtistsComponent implements OnInit, OnDestroy {
const artists: ArtistModel[] = this.getArtistsByDisplayNames(displayNames);
this.getAlbumsForArtists(artists);
this.getTracksForArtists(artists);
this.selectedArtists = artists;
}),
);

Expand Down Expand Up @@ -136,6 +139,7 @@ export class CollectionArtistsComponent implements OnInit, OnDestroy {
this.artists = [];
this.albums = [];
this.tracks = new TrackModels();
this.selectedArtists = [];
}

private getArtists(): void {
Expand All @@ -146,6 +150,7 @@ export class CollectionArtistsComponent implements OnInit, OnDestroy {
private getAlbums(): void {
const selectedArtists: ArtistModel[] = this.artistsPersister.getSelectedArtists(this.artists);
this.getAlbumsForArtists(selectedArtists);
this.selectedArtists = selectedArtists;
}

private getTracks(): void {
Expand Down
Loading