diff --git a/approot/tracklist.xml b/approot/tracklist.xml
index 093a7884..bb1af88b 100644
--- a/approot/tracklist.xml
+++ b/approot/tracklist.xml
@@ -6,6 +6,7 @@
${name}
${track-count} ยท ${duration}
+ ${clusters class="mb-2"}
${play-btn class="btn btn-primary"}
diff --git a/src/libs/services/database/impl/TrackList.cpp b/src/libs/services/database/impl/TrackList.cpp
index 504e09b9..4f5dd555 100644
--- a/src/libs/services/database/impl/TrackList.cpp
+++ b/src/libs/services/database/impl/TrackList.cpp
@@ -466,6 +466,57 @@ TrackList::getClusters() const
return std::vector(res.begin(), res.end());
}
+std::vector>
+TrackList::getClusterGroups(const std::vector& clusterTypes, std::size_t size) const
+{
+ assert(session());
+ std::vector> res;
+
+ if (clusterTypes.empty())
+ return res;
+
+ auto query {session()->query>("SELECT c from cluster c")};
+
+ query.join("track t ON c.id = t_c.cluster_id")
+ .join("track_cluster t_c ON t_c.track_id = t.id")
+ .join("cluster_type c_type ON c.cluster_type_id = c_type.id")
+ .join("tracklist_entry t_l_e ON t_l_e.track_id = t.id")
+ .join("tracklist t_l ON t_l.id = t_l_e.tracklist_id")
+ .where("t_l.id = ?").bind(getId());
+
+ {
+ std::ostringstream oss;
+ oss << "c_type.id IN (";
+ bool first {true};
+ for (auto clusterType : clusterTypes)
+ {
+ if (!first)
+ oss << ", ";
+ oss << "?";
+ query.bind(clusterType ->getId());
+ first = false;
+ }
+ oss << ")";
+ query.where(oss.str());
+ }
+ query.groupBy("c.id");
+ query.orderBy("COUNT(c.id) DESC");
+
+ auto queryRes {query.resultList()};
+
+ std::map> clustersByType;
+ for (const Wt::Dbo::ptr& cluster : queryRes)
+ {
+ if (clustersByType[cluster->getType()->getId()].size() < size)
+ clustersByType[cluster->getType()->getId()].push_back(cluster);
+ }
+
+ for (const auto& [clusterTypeId, clusters] : clustersByType)
+ res.push_back(clusters);
+
+ return res;
+}
+
bool
TrackList::hasTrack(TrackId trackId) const
{
diff --git a/src/libs/services/database/include/services/database/TrackList.hpp b/src/libs/services/database/include/services/database/TrackList.hpp
index 5eeb7d94..419278db 100644
--- a/src/libs/services/database/include/services/database/TrackList.hpp
+++ b/src/libs/services/database/include/services/database/TrackList.hpp
@@ -38,6 +38,7 @@ namespace Database {
class Artist;
class Cluster;
+class ClusterType;
class Release;
class Session;
class Track;
@@ -107,6 +108,7 @@ class TrackList : public Object
// Get clusters, order by occurence
std::vector> getClusters() const;
+ std::vector>> getClusterGroups(const std::vector>& clusterTypes, std::size_t size) const;
bool hasTrack(TrackId trackId) const;
diff --git a/src/lms/ui/explore/TrackListView.cpp b/src/lms/ui/explore/TrackListView.cpp
index 7926a174..22465030 100644
--- a/src/lms/ui/explore/TrackListView.cpp
+++ b/src/lms/ui/explore/TrackListView.cpp
@@ -21,6 +21,8 @@
#include
+#include "services/database/Cluster.hpp"
+#include "services/database/ScanSettings.hpp"
#include "services/database/Session.hpp"
#include "services/database/Track.hpp"
#include "services/database/TrackList.hpp"
@@ -51,7 +53,8 @@ namespace
namespace UserInterface
{
TrackList::TrackList(Filters& filters)
- : Template {Wt::WString::tr("Lms.Explore.TrackList.template")}
+ : Template {Wt::WString::tr("Lms.Explore.TrackList.template")}
+ , _filters {filters}
{
addFunction("tr", &Wt::WTemplate::Functions::tr);
addFunction("id", &Wt::WTemplate::Functions::id);
@@ -61,7 +64,7 @@ namespace UserInterface
refreshView();
});
- filters.updated().connect([this]
+ _filters.updated().connect([this]
{
refreshView();
});
@@ -93,6 +96,25 @@ namespace UserInterface
bindString("duration", Utils::durationToString(trackList->getDuration()));
bindString("track-count", Wt::WString::trn("Lms.Explore.TrackList.track-count", trackList->getCount()).arg(trackList->getCount()));
+ Wt::WContainerWidget* clusterContainers {bindNew("clusters")};
+ {
+ const auto clusterTypes {ScanSettings::get(LmsApp->getDbSession())->getClusterTypes()};
+ const auto clusterGroups {trackList->getClusterGroups(clusterTypes, 3)};
+
+ for (const auto& clusters : clusterGroups)
+ {
+ for (const Database::Cluster::pointer& cluster : clusters)
+ {
+ const ClusterId clusterId {cluster->getId()};
+ Wt::WInteractWidget* entry {clusterContainers->addWidget(Utils::createCluster(clusterId))};
+ entry->clicked().connect([=]
+ {
+ _filters.add(clusterId);
+ });
+ }
+ }
+ }
+
bindNew("play-btn", Wt::WString::tr("Lms.Explore.play"), Wt::TextFormat::XHTML)
->clicked().connect([=]
{
@@ -163,6 +185,7 @@ namespace UserInterface
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
Database::Track::FindParameters params;
+ params.setClusters(_filters.getClusterIds());
params.setTrackList(_trackListId);
params.setSortMethod(Database::TrackSortMethod::TrackList);
params.setRange({static_cast(_container->getCount()), _batchSize});
diff --git a/src/lms/ui/explore/TrackListView.hpp b/src/lms/ui/explore/TrackListView.hpp
index a8b2ec5a..f19c8bb9 100644
--- a/src/lms/ui/explore/TrackListView.hpp
+++ b/src/lms/ui/explore/TrackListView.hpp
@@ -47,6 +47,7 @@ namespace UserInterface
static constexpr std::size_t _batchSize {6};
static constexpr std::size_t _maxCount {8000};
+ Filters& _filters;
Database::TrackListId _trackListId;
InfiniteScrollingContainer* _container {};
};