diff --git a/docroot/css/lms.css b/docroot/css/lms.css
index d8020abc..c2ff47ac 100644
--- a/docroot/css/lms.css
+++ b/docroot/css/lms.css
@@ -126,6 +126,7 @@ a.Lms-artistname:hover, a.Lms-artistname:focus {
.Lms-explore-entry-list {
padding-top: 4px;
padding-bottom: 4px;
+ min-height: 76px;
}
.Lms-explore-entry-list-text-container {
@@ -229,6 +230,10 @@ a.Lms-artistname:hover, a.Lms-artistname:focus {
margin-bottom: 15px;
}
+.Lms-explore-artist-tracks-container {
+ margin-bottom: 15px;
+}
+
.Lms-explore-mode-container {
margin-bottom: 8px;
}
diff --git a/src/libs/database/impl/Artist.cpp b/src/libs/database/impl/Artist.cpp
index 368de2ed..91925b58 100644
--- a/src/libs/database/impl/Artist.cpp
+++ b/src/libs/database/impl/Artist.cpp
@@ -465,15 +465,16 @@ Artist::getTracks(std::optional
linkType) const
}
std::vector>
-Artist::getTracksWithRelease(std::optional linkType) const
+Artist::getNonReleaseTracks(std::optional linkType) const
{
assert(self());
assert(IdIsValid(self()->id()));
assert(session());
- auto query {session()->query>("SELECT t FROM track t INNER JOIN artist a ON a.id = t_a_l.artist_id INNER JOIN track_artist_link t_a_l ON t_a_l.track_id = t.id INNER JOIN release r ON r.id = t.release_id")
+ auto query {session()->query>("SELECT t FROM track t INNER JOIN artist a ON a.id = t_a_l.artist_id INNER JOIN track_artist_link t_a_l ON t_a_l.track_id = t.id")
.where("a.id = ?").bind(self()->id())
- .orderBy("t.year,r.name,t.disc_number,t.track_number")};
+ .where("t.release_id is NULL")
+ .orderBy("t.name")};
if (linkType)
query.where("t_a_l.type = ?").bind(*linkType);
diff --git a/src/libs/database/include/database/Artist.hpp b/src/libs/database/include/database/Artist.hpp
index 0b252d59..824243f8 100644
--- a/src/libs/database/include/database/Artist.hpp
+++ b/src/libs/database/include/database/Artist.hpp
@@ -104,7 +104,7 @@ class Artist : public Wt::Dbo::Dbo
std::vector> getReleases(const std::set& clusterIds = {}) const; // if non empty, get the releases that match all these clusters
std::size_t getReleaseCount() const;
std::vector> getTracks(std::optional linkType = {}) const;
- std::vector> getTracksWithRelease(std::optional linkType = {}) const;
+ std::vector> getNonReleaseTracks(std::optional linkType = {}) const;
std::vector> getRandomTracks(std::optional count) const;
// No artistLinkTypes means get them all
diff --git a/src/lms/ui/explore/ArtistView.cpp b/src/lms/ui/explore/ArtistView.cpp
index 68b428d6..1a1be090 100644
--- a/src/lms/ui/explore/ArtistView.cpp
+++ b/src/lms/ui/explore/ArtistView.cpp
@@ -29,6 +29,7 @@
#include "database/Release.hpp"
#include "database/ScanSettings.hpp"
#include "database/Session.hpp"
+#include "database/Track.hpp"
#include "database/User.hpp"
#include "recommendation/IEngine.hpp"
#include "utils/Logger.hpp"
@@ -40,6 +41,7 @@
#include "LmsApplication.hpp"
#include "LmsApplicationException.hpp"
#include "ReleaseListHelpers.hpp"
+#include "TrackListHelpers.hpp"
using namespace Database;
@@ -107,6 +109,8 @@ Artist::refreshView()
if (!artist)
throw ArtistNotFoundException {};
+ refreshReleases(artist);
+ refreshNonReleaseTracks(artist);
refreshLinks(artist);
refreshSimilarArtists(similarArtistIds);
@@ -185,16 +189,40 @@ Artist::refreshView()
popup->exec(moreBtn);
});
}
+}
+
+void
+Artist::refreshReleases(const Wt::Dbo::ptr& artist)
+{
+ const auto releases {artist->getReleases(_filters->getClusterIds())};
+ if (releases.empty())
+ return;
+
+ setCondition("if-has-release", true);
Wt::WContainerWidget* releasesContainer = bindNew("releases");
-
- auto releases = artist->getReleases(_filters->getClusterIds());
for (const auto& release : releases)
{
releasesContainer->addWidget(ReleaseListHelpers::createEntryForArtist(release, artist));
}
}
+void
+Artist::refreshNonReleaseTracks(const Wt::Dbo::ptr& artist)
+{
+ const auto tracks {artist->getNonReleaseTracks()};
+ if (tracks.empty())
+ return;
+
+ setCondition("if-has-non-release-track", true);
+
+ Wt::WContainerWidget* tracksContainer = bindNew("tracks");
+ for (const Track::pointer& track : tracks)
+ {
+ tracksContainer->addWidget(TrackListHelpers::createEntry(track, tracksAction));
+ }
+}
+
void
Artist::refreshSimilarArtists(const std::unordered_set& similarArtistsId)
{
diff --git a/src/lms/ui/explore/ArtistView.hpp b/src/lms/ui/explore/ArtistView.hpp
index 5b9fe034..3f17238b 100644
--- a/src/lms/ui/explore/ArtistView.hpp
+++ b/src/lms/ui/explore/ArtistView.hpp
@@ -44,9 +44,12 @@ class Artist : public Wt::WTemplate
Artist(Filters* filters);
PlayQueueActionSignal artistsAction;
+ PlayQueueActionSignal tracksAction;
private:
void refreshView();
+ void refreshReleases(const Wt::Dbo::ptr& artist);
+ void refreshNonReleaseTracks(const Wt::Dbo::ptr& artist);
void refreshSimilarArtists(const std::unordered_set& similarArtistsId);
void refreshLinks(const Wt::Dbo::ptr& artist);
diff --git a/src/lms/ui/explore/Explore.cpp b/src/lms/ui/explore/Explore.cpp
index 06c32bd0..de7a1f6f 100644
--- a/src/lms/ui/explore/Explore.cpp
+++ b/src/lms/ui/explore/Explore.cpp
@@ -93,6 +93,7 @@ Explore::Explore(Filters* filters)
auto artist = std::make_unique(_filters);
artist->artistsAction.connect(this, &Explore::handleArtistsAction);
+ artist->tracksAction.connect(this, &Explore::handleTracksAction);
contentsStack->addWidget(std::move(artist));
auto releases = std::make_unique(*_filters);
diff --git a/src/test/database/Artist.cpp b/src/test/database/Artist.cpp
index 4c16f5fc..d32d7c0e 100644
--- a/src/test/database/Artist.cpp
+++ b/src/test/database/Artist.cpp
@@ -312,3 +312,27 @@ TEST_F(DatabaseFixture, MultiArtistsSortMethod)
}
}
+TEST_F(DatabaseFixture, SingleArtistNonReleaseTracks)
+{
+ ScopedArtist artist {session, "artist"};
+ ScopedTrack track1 {session, "MyTrack1"};
+ ScopedTrack track2 {session, "MyTrack2"};
+ ScopedRelease release{session, "MyRelease"};
+ {
+ auto transaction {session.createUniqueTransaction()};
+
+ TrackArtistLink::create(session, track1.get(), artist.get(), TrackArtistLinkType::Artist);
+ TrackArtistLink::create(session, track2.get(), artist.get(), TrackArtistLinkType::Artist);
+
+ track1.get().modify()->setRelease(release.get());
+ }
+
+
+ {
+ auto transaction {session.createSharedTransaction()};
+
+ const auto tracks {artist->getNonReleaseTracks()};
+ ASSERT_EQ(tracks.size(), 1);
+ EXPECT_EQ(tracks.front().id(), track2.getId());
+ }
+}