diff --git a/approot/artist.xml b/approot/artist.xml index 21f3e7a9..f1e839d8 100644 --- a/approot/artist.xml +++ b/approot/artist.xml @@ -8,9 +8,20 @@ ${clusters} ${play-btn class="Lms-explore-btn Lms-btn"}${more-btn class="Lms-explore-btn Lms-btn"} + ${}
${releases class="Lms-explore-artist-entries-container"}
+ ${
} + ${} +
+
+
+ ${tracks} +
+
+
+ ${
}
${}
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()); + } +}