diff --git a/approot/artists.xml b/approot/artists.xml index e7c51a3d..519618ce 100644 --- a/approot/artists.xml +++ b/approot/artists.xml @@ -6,10 +6,17 @@ -
-
- - ${search} +
+
+
+ ${link-type} +
+
+
+
+ + ${search} +
${artists} diff --git a/approot/messages.xml b/approot/messages.xml index 0a3eb8e2..17d5ded8 100644 --- a/approot/messages.xml +++ b/approot/messages.xml @@ -115,6 +115,9 @@ Similar artists +All artists +Track artists +Album artists Recently added Top artists diff --git a/approot/messages_fr.xml b/approot/messages_fr.xml index 4bb46d9f..2d61dc1d 100644 --- a/approot/messages_fr.xml +++ b/approot/messages_fr.xml @@ -115,6 +115,9 @@ Artistes similaires +Tous les artistes +Artiste de piste +Artiste d'album Ajouts récents Artistes populaires diff --git a/docroot/css/lms.css b/docroot/css/lms.css index 5a756d69..a9d84bb6 100644 --- a/docroot/css/lms.css +++ b/docroot/css/lms.css @@ -232,6 +232,10 @@ a:hover { font-style: normal; } +.Lms-explore-artists-input { + margin-bottom: 10px; +} + .Lms-explore-artist-entry { padding-top: 8px; padding-bottom: 8px; diff --git a/src/api/subsonic/SubsonicResource.cpp b/src/api/subsonic/SubsonicResource.cpp index 30f1270c..3a4d2568 100644 --- a/src/api/subsonic/SubsonicResource.cpp +++ b/src/api/subsonic/SubsonicResource.cpp @@ -1387,7 +1387,7 @@ handleSearchRequestCommon(RequestContext& context, bool id3) bool more; { - auto artists {Artist::getByFilter(context.dbSession, {}, keywords, artistOffset, artistCount, more)}; + auto artists {Artist::getByFilter(context.dbSession, {}, keywords, {}, artistOffset, artistCount, more)}; for (const Artist::pointer& artist : artists) searchResult2Node.addArrayChild("artist", artistToResponseNode(user, artist, id3)); } diff --git a/src/database/Artist.cpp b/src/database/Artist.cpp index 0b457f1f..50dec877 100644 --- a/src/database/Artist.cpp +++ b/src/database/Artist.cpp @@ -108,7 +108,8 @@ static Wt::Dbo::Query getQuery(Session& session, const std::set& clusterIds, - const std::vector& keywords) + const std::vector& keywords, + std::optional linkType) { session.checkSharedLocked(); @@ -120,16 +121,22 @@ getQuery(Session& session, for (auto keyword : keywords) where.And(WhereClause("a.name LIKE ?")).bind("%%" + keyword + "%%"); - if (!clusterIds.empty()) + if (!clusterIds.empty() || linkType) { - oss << " INNER JOIN track t ON t.id = t_a_l.track_id INNER JOIN track_artist_link t_a_l ON t_a_l.artist_id = a.id INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id"; + oss << " INNER JOIN track t ON t.id = t_a_l.track_id INNER JOIN track_artist_link t_a_l ON t_a_l.artist_id = a.id"; - WhereClause clusterClause; + if (!clusterIds.empty()) + { + oss << " INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id"; + WhereClause clusterClause; - for (auto id : clusterIds) - clusterClause.Or(WhereClause("c.id = ?")).bind(std::to_string(id)); + for (auto id : clusterIds) + clusterClause.Or(WhereClause("c.id = ?")).bind(std::to_string(id)); - where.And(clusterClause); + where.And(clusterClause); + } + if (linkType) + where.And(WhereClause {"t_a_l.type = ?"}.bind(std::to_string(static_cast(*linkType)))); } oss << " " << where.get(); @@ -155,19 +162,20 @@ Artist::getByClusters(Session& session, const std::set& clusters) session.checkSharedLocked(); bool more; - return getByFilter(session, clusters, {}, {}, {}, more); + return getByFilter(session, clusters, {}, {}, {}, {}, more); } std::vector Artist::getByFilter(Session& session, const std::set& clusters, const std::vector& keywords, + std::optional linkType, std::optional offset, std::optional size, bool& moreResults) { session.checkSharedLocked(); - Wt::Dbo::collection collection = getQuery(session, clusters, keywords) + Wt::Dbo::collection collection = getQuery(session, clusters, keywords, linkType) .limit(size ? static_cast(*size) + 1 : -1) .offset(offset ? static_cast(*offset) : -1); diff --git a/src/database/Artist.hpp b/src/database/Artist.hpp index fb03bf8b..9357c666 100644 --- a/src/database/Artist.hpp +++ b/src/database/Artist.hpp @@ -57,6 +57,7 @@ class Artist : public Wt::Dbo::Dbo static std::vector getByFilter(Session& session, const std::set& clusters, // if non empty, at least one artist that belongs to these clusters const std::vector& keywords, // if non empty, name must match all of these keywords + std::optional linkType, // if set, only artists that have produced at least one track with this link type std::optional offset, std::optional size, bool& moreExpected); diff --git a/src/ui/explore/ArtistsView.cpp b/src/ui/explore/ArtistsView.cpp index c207e551..8fbceefb 100644 --- a/src/ui/explore/ArtistsView.cpp +++ b/src/ui/explore/ArtistsView.cpp @@ -19,13 +19,15 @@ #include "ArtistsView.hpp" +#include + #include #include #include #include +#include "common/ValueStringModel.hpp" #include "database/Artist.hpp" - #include "utils/Logger.hpp" #include "utils/Utils.hpp" @@ -36,6 +38,8 @@ using namespace Database; namespace UserInterface { +using ArtistLinkModel = ValueStringModel>; + Artists::Artists(Filters* filters) : Wt::WTemplate(Wt::WString::tr("Lms.Explore.Artists.template")), _filters(filters) @@ -46,6 +50,16 @@ Artists::Artists(Filters* filters) _search->setPlaceholderText(Wt::WString::tr("Lms.Explore.search-placeholder")); _search->textInput().connect(this, &Artists::refresh); + _linkType = bindNew("link-type"); + { + auto linkTypeModel {std::make_shared()}; + linkTypeModel->add(Wt::WString::tr("Lms.Explore.Artists.linktype-all"), {}); + linkTypeModel->add(Wt::WString::tr("Lms.Explore.Artists.linktype-artist"), TrackArtistLink::Type::Artist); + linkTypeModel->add(Wt::WString::tr("Lms.Explore.Artists.linktype-releaseartist"), TrackArtistLink::Type::ReleaseArtist); + _linkType->setModel(linkTypeModel); + } + _linkType->changed().connect(this, &Artists::refresh); + _container = bindNew("artists"); _showMore = bindNew("show-more", Wt::WString::tr("Lms.Explore.show-more")); @@ -72,6 +86,7 @@ Artists::addSome() auto searchKeywords = splitString(_search->text().toUTF8(), " "); auto clusterIds = _filters->getClusterIds(); + auto linkModel = static_cast(_linkType->model().get()); auto transaction {LmsApp->getDbSession().createSharedTransaction()}; @@ -79,6 +94,7 @@ Artists::addSome() const std::vector artists {Artist::getByFilter(LmsApp->getDbSession(), clusterIds, searchKeywords, + linkModel->getValue(_linkType->currentIndex()), _container->count(), 20, moreResults)}; for (const auto& artist : artists) diff --git a/src/ui/explore/ArtistsView.hpp b/src/ui/explore/ArtistsView.hpp index 03f33e10..169c7088 100644 --- a/src/ui/explore/ArtistsView.hpp +++ b/src/ui/explore/ArtistsView.hpp @@ -19,6 +19,7 @@ #pragma once +#include #include #include #include @@ -46,6 +47,7 @@ class Artists : public Wt::WTemplate Filters* _filters; Wt::WPushButton* _showMore; Wt::WLineEdit* _search; + Wt::WComboBox* _linkType; Wt::WContainerWidget* _container; }; diff --git a/test/database/DatabaseTest.cpp b/test/database/DatabaseTest.cpp index 26ecf7a5..8a19b486 100644 --- a/test/database/DatabaseTest.cpp +++ b/test/database/DatabaseTest.cpp @@ -306,6 +306,16 @@ testSingleTrackSingleArtistMultiRoles(Session& session) CHECK(Artist::getAllOrphans(session).empty()); } + { + auto transaction {session.createSharedTransaction()}; + bool hasMore{}; + CHECK(Artist::getByFilter(session, {}, {}, {}, {}, {}, hasMore).size() == 1); + CHECK(Artist::getByFilter(session, {}, {}, TrackArtistLink::Type::Artist, {}, {}, hasMore).size() == 1); + CHECK(Artist::getByFilter(session, {}, {}, TrackArtistLink::Type::ReleaseArtist, {}, {}, hasMore).size() == 1); + CHECK(Artist::getByFilter(session, {}, {}, TrackArtistLink::Type::Writer, {}, {}, hasMore).size() == 1); + CHECK(Artist::getByFilter(session, {}, {}, TrackArtistLink::Type::Composer, {}, {}, hasMore).empty()); + } + { auto transaction {session.createSharedTransaction()};