- ${cover class="shadow-sm"}
+
+
+
+ ${cover class="shadow-sm"}
+
+ ${release-name class="d-block text-truncate text-nowrap text-decoration-none link-success"}
+ ${
}${artist-name class="d-block text-truncate text-nowrap text-decoration-none link-secondary"}${}
+ ${
}${tr:Lms.Explore.various-artists}
${}
+ ${
}${year}
${}
+
- ${release-name class="d-block text-truncate text-nowrap text-decoration-none link-success"}
- ${
}${artist-name class="d-block text-truncate text-nowrap text-decoration-none link-secondary"}${}
- ${
}${tr:Lms.Explore.various-artists}
${}
- ${
}${year}
${}
- ${elements class="row gy-4"}
+ ${elements class="row row-cols-2 row-cols-md-3 row-cols-lg-4 row-cols-xl-6 gx-2 gy-4"}
${loading-indicator}
diff --git a/approot/settings.xml b/approot/settings.xml
index c17ef13a..83585400 100644
--- a/approot/settings.xml
+++ b/approot/settings.xml
@@ -9,7 +9,7 @@
${tr:Lms.Settings.audio-settings-are-local}
-
+
@@ -18,7 +18,7 @@
${transcode-mode-info}
-
+
@@ -27,7 +27,7 @@
${transcode-format-info}
-
+
@@ -39,7 +39,7 @@
${transcode-bitrate-info}
-
+
@@ -48,7 +48,7 @@
${replaygain-mode-info}
-
+
@@ -60,7 +60,7 @@
${replaygain-preamp-info}
-
+
@@ -117,7 +117,7 @@
${}
-
+
@@ -126,7 +126,7 @@
${scrobbler-info class="help-block"}
-
+
@@ -148,7 +148,7 @@
${}
-
+
@@ -157,7 +157,7 @@
${password-info class="help-block"}
-
+
diff --git a/src/libs/services/database/impl/Artist.cpp b/src/libs/services/database/impl/Artist.cpp
index 6a7428a0..575cb675 100644
--- a/src/libs/services/database/impl/Artist.cpp
+++ b/src/libs/services/database/impl/Artist.cpp
@@ -204,8 +204,8 @@ Artist::find(Session& session, const FindParameters& params)
return execQuery(query, params.range);
}
-std::vector
-Artist::getReleases(const std::vector& clusterIds) const
+RangeResults
+Artist::getReleases(Range range, const std::vector& clusterIds) const
{
assert(session());
@@ -213,7 +213,7 @@ Artist::getReleases(const std::vector& clusterIds) const
std::ostringstream oss;
- oss << "SELECT DISTINCT r FROM release r 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 track t ON t.release_id = r.id";
+ oss << "SELECT DISTINCT r.id FROM release r 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 track t ON t.release_id = r.id";
if (!clusterIds.empty())
{
@@ -236,13 +236,12 @@ Artist::getReleases(const std::vector& clusterIds) const
oss << " ORDER BY t.date DESC, r.name COLLATE NOCASE";
- auto query {session()->query>(oss.str())};
+ auto query {session()->query(oss.str())};
for (const std::string& bindArg : where.getBindArgs())
query.bind(bindArg);
- auto res {query.resultList()};
- return std::vector(res.begin(), res.end());
+ return execQuery(query, range);
}
std::size_t
diff --git a/src/libs/services/database/include/services/database/Artist.hpp b/src/libs/services/database/include/services/database/Artist.hpp
index ac94ab3d..8fd4b1a9 100644
--- a/src/libs/services/database/include/services/database/Artist.hpp
+++ b/src/libs/services/database/include/services/database/Artist.hpp
@@ -30,6 +30,7 @@
#include "services/database/ArtistId.hpp"
#include "services/database/ClusterId.hpp"
#include "services/database/Object.hpp"
+#include "services/database/ReleaseId.hpp"
#include "services/database/Types.hpp"
#include "services/database/UserId.hpp"
#include "utils/EnumSet.hpp"
@@ -88,7 +89,7 @@ class Artist : public Object
const std::string& getSortName() const { return _sortName; }
std::optional getMBID() const { return UUID::fromString(_MBID); }
- std::vector> getReleases(const std::vector& clusterIds = {}) const; // if non empty, get the releases that match all these clusters
+ RangeResults getReleases(Range range, const std::vector& 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;
bool hasNonReleaseTracks(std::optional linkType = std::nullopt) const;
diff --git a/src/libs/services/database/test/Cluster.cpp b/src/libs/services/database/test/Cluster.cpp
index df6c677d..76ff7be0 100644
--- a/src/libs/services/database/test/Cluster.cpp
+++ b/src/libs/services/database/test/Cluster.cpp
@@ -574,13 +574,13 @@ TEST_F(DatabaseFixture, SingleTrackSingleReleaseSingleArtistSingleCluster)
ASSERT_EQ(artists.results.size(), 1);
EXPECT_EQ(artists.results.front(), artist.getId());
- auto releases {artist->getReleases()};
- ASSERT_EQ(releases.size(), 1);
- EXPECT_EQ(releases.front()->getId(), release.getId());
+ auto releases {artist->getReleases(Range {})};
+ ASSERT_EQ(releases.results.size(), 1);
+ EXPECT_EQ(releases.results.front(), release.getId());
- releases = artist->getReleases({cluster.getId()});
- ASSERT_EQ(releases.size(), 1);
- EXPECT_EQ(releases.front()->getId(), release.getId());
+ releases = artist->getReleases(Range {}, {cluster.getId()});
+ ASSERT_EQ(releases.results.size(), 1);
+ EXPECT_EQ(releases.results.front(), release.getId());
}
}
@@ -605,13 +605,13 @@ TEST_F(DatabaseFixture, SingleTrackSingleReleaseSingleArtistMultiClusters)
{
auto transaction {session.createSharedTransaction()};
- auto releases {artist->getReleases()};
- ASSERT_EQ(releases.size(), 1);
- EXPECT_EQ(releases.front()->getId(), release.getId());
+ auto releases {artist->getReleases(Range {})};
+ ASSERT_EQ(releases.results.size(), 1);
+ EXPECT_EQ(releases.results.front(), release.getId());
- releases = artist->getReleases({cluster1.getId(), cluster2.getId()});
- ASSERT_EQ(releases.size(), 1);
- EXPECT_EQ(releases.front()->getId(), release.getId());
+ releases = artist->getReleases(Range {}, {cluster1.getId(), cluster2.getId()});
+ ASSERT_EQ(releases.results.size(), 1);
+ EXPECT_EQ(releases.results.front(), release.getId());
}
}
diff --git a/src/libs/services/database/test/DatabaseTest.cpp b/src/libs/services/database/test/DatabaseTest.cpp
index 918d519a..adf53728 100644
--- a/src/libs/services/database/test/DatabaseTest.cpp
+++ b/src/libs/services/database/test/DatabaseTest.cpp
@@ -50,8 +50,9 @@ TEST_F(DatabaseFixture, MultiTracksSingleArtistSingleRelease)
auto transaction {session.createSharedTransaction()};
EXPECT_EQ(artist->getReleaseCount(), 1);
- ASSERT_EQ(artist->getReleases().size(), 1);
- EXPECT_EQ(artist->getReleases().front()->getId(), release.getId());
+ auto releases {artist->getReleases(Range {})};
+ ASSERT_EQ(releases.results.size(), 1);
+ EXPECT_EQ(releases.results.front(), release.getId());
EXPECT_EQ(release->getTracks().size(), nbTracks);
}
@@ -73,9 +74,9 @@ TEST_F(DatabaseFixture, SingleTrackSingleReleaseSingleArtist)
{
auto transaction {session.createUniqueTransaction()};
- auto releases {artist->getReleases()};
- ASSERT_EQ(releases.size(), 1);
- EXPECT_EQ(releases.front()->getId(), release.getId());
+ auto releases {artist->getReleases(Range {})};
+ ASSERT_EQ(releases.results.size(), 1);
+ EXPECT_EQ(releases.results.front(), release.getId());
EXPECT_EQ(artist->getReleaseCount(), 1);
diff --git a/src/libs/subsonic/impl/SubsonicResource.cpp b/src/libs/subsonic/impl/SubsonicResource.cpp
index 0f4f627c..df5228e5 100644
--- a/src/libs/subsonic/impl/SubsonicResource.cpp
+++ b/src/libs/subsonic/impl/SubsonicResource.cpp
@@ -885,9 +885,12 @@ handleGetArtistRequest(RequestContext& context)
Response response {Response::createOkResponse(context.serverProtocolVersion)};
Response::Node artistNode {artistToResponseNode(user, artist, true /* id3 */)};
- auto releases {artist->getReleases()};
- for (const Release::pointer& release : releases)
+ auto releases {artist->getReleases(Range {})};
+ for (const ReleaseId releaseId : releases.results)
+ {
+ const Release::pointer release {Release::find(context.dbSession, releaseId)};
artistNode.addArrayChild("album", releaseToResponseNode(release, context.dbSession, user, true /* id3 */));
+ }
response.addNode("artist", std::move(artistNode));
@@ -997,9 +1000,12 @@ handleGetMusicDirectoryRequest(RequestContext& context)
directoryNode.setAttribute("name", makeNameFilesystemCompatible(artist->getName()));
- auto releases {artist->getReleases()};
- for (const Release::pointer& release : releases)
+ auto releases {artist->getReleases(Range {})};
+ for (const ReleaseId releaseId : releases.results)
+ {
+ const Release::pointer release {Release::find(context.dbSession, releaseId)};
directoryNode.addArrayChild("child", releaseToResponseNode(release, context.dbSession, user, false /* no id3 */));
+ }
}
else if (releaseId)
{
diff --git a/src/lms/ui/LmsApplication.cpp b/src/lms/ui/LmsApplication.cpp
index 0b541df6..9aef8471 100644
--- a/src/lms/ui/LmsApplication.cpp
+++ b/src/lms/ui/LmsApplication.cpp
@@ -23,7 +23,6 @@
#include
#include
#include
-#include
#include
#include
#include
@@ -330,13 +329,6 @@ LmsApplication::createCluster(Database::Cluster::pointer cluster, bool canDelete
return res;
}
-Wt::WPopupMenu*
-LmsApplication::createPopupMenu()
-{
- _popupMenu = std::make_unique();
- return _popupMenu.get();
-}
-
void
LmsApplication::handleException(LmsApplicationException& e)
{
diff --git a/src/lms/ui/LmsApplication.hpp b/src/lms/ui/LmsApplication.hpp
index 41198fd0..eae65285 100644
--- a/src/lms/ui/LmsApplication.hpp
+++ b/src/lms/ui/LmsApplication.hpp
@@ -38,10 +38,6 @@ namespace Database
class Session;
class User;
}
-namespace Wt
-{
- class WPopupMenu;
-}
namespace UserInterface {
@@ -88,7 +84,6 @@ class LmsApplication : public Wt::WApplication
static Wt::WLink createReleaseLink(Database::ObjectPtr release);
static std::unique_ptr createReleaseAnchor(Database::ObjectPtr release, bool addText = true);
static std::unique_ptr createCluster(Database::ObjectPtr cluster, bool canDelete = false);
- Wt::WPopupMenu* createPopupMenu();
MediaPlayer& getMediaPlayer() const { return *_mediaPlayer; }
PlayQueue& getPlayQueue() const { return *_playQueue; }
@@ -125,7 +120,6 @@ class LmsApplication : public Wt::WApplication
std::shared_ptr _coverResource;
MediaPlayer* _mediaPlayer {};
PlayQueue* _playQueue {};
- std::unique_ptr _popupMenu;
NotificationContainer* _notificationContainer {};
ModalManager* _modalManager {};
};
diff --git a/src/lms/ui/PlayQueue.cpp b/src/lms/ui/PlayQueue.cpp
index d421f811..ab1f9c65 100644
--- a/src/lms/ui/PlayQueue.cpp
+++ b/src/lms/ui/PlayQueue.cpp
@@ -489,7 +489,7 @@ PlayQueue::addEntry(const Database::TrackListEntry::pointer& tracklistEntry)
Wt::WText* moreBtn {entry->bindNew("more-btn", Wt::WString::tr("Lms.PlayQueue.template.more-btn"), Wt::TextFormat::XHTML)};
moreBtn->clicked().connect([=]
{
- Wt::WPopupMenu* popup {LmsApp->createPopupMenu()};
+/* Wt::WPopupMenu* popup {LmsApp->createPopupMenu()};
const bool isStarred {Service::get()->isStarred(LmsApp->getUserId(), trackId)};
popup->addItem(Wt::WString::tr(isStarred ? "Lms.Explore.unstar" : "Lms.Explore.star"))
@@ -504,6 +504,7 @@ PlayQueue::addEntry(const Database::TrackListEntry::pointer& tracklistEntry)
->setLink(Wt::WLink {std::make_unique(trackId)});
popup->popup(moreBtn);
+ */
});
}
diff --git a/src/lms/ui/explore/ArtistView.cpp b/src/lms/ui/explore/ArtistView.cpp
index 0d817647..e98d0254 100644
--- a/src/lms/ui/explore/ArtistView.cpp
+++ b/src/lms/ui/explore/ArtistView.cpp
@@ -180,17 +180,16 @@ Artist::refreshView()
void
Artist::refreshReleases(const ObjectPtr& artist)
{
- const auto releases {artist->getReleases(_filters->getClusterIds())};
- if (releases.empty())
+ if (artist->getReleaseCount() == 0)
return;
setCondition("if-has-release", true);
-
- Wt::WContainerWidget* releasesContainer = bindNew("releases");
- for (const auto& release : releases)
+ _releaseContainer = bindNew("releases", Wt::WString::tr("Lms.Explore.Releases.template.container"));
+ _releaseContainer->onRequestElements.connect(this, [this]
{
- releasesContainer->addWidget(ReleaseListHelpers::createEntryForArtist(release, artist));
- }
+ addSomeReleases();
+ });
+ addSomeReleases();
}
void
@@ -239,6 +238,27 @@ Artist::refreshLinks(const Database::Artist::pointer& artist)
}
}
+void
+Artist::addSomeReleases()
+{
+ auto transaction {LmsApp->getDbSession().createSharedTransaction()};
+
+ const Database::Artist::pointer artist {Database::Artist::find(LmsApp->getDbSession(), _artistId)};
+ if (!artist)
+ return;
+
+ const Range range {static_cast(_releaseContainer->getCount()), _releasesBatchSize};
+ const auto releases {artist->getReleases(range, _filters->getClusterIds())};
+
+ for (const ReleaseId releaseId : releases.results)
+ {
+ const Database::Release::pointer release {Database::Release::find(LmsApp->getDbSession(), releaseId)};
+ _releaseContainer->add(ReleaseListHelpers::createEntryForArtist(release, artist));
+ }
+
+ _releaseContainer->setHasMore(releases.moreResults);
+}
+
void
Artist::addSomeNonReleaseTracks()
{
diff --git a/src/lms/ui/explore/ArtistView.hpp b/src/lms/ui/explore/ArtistView.hpp
index e6e2933e..9b7dabb5 100644
--- a/src/lms/ui/explore/ArtistView.hpp
+++ b/src/lms/ui/explore/ArtistView.hpp
@@ -51,11 +51,14 @@ namespace UserInterface
void refreshSimilarArtists(const std::vector& similarArtistsId);
void refreshLinks(const Database::ObjectPtr& artist);
+ void addSomeReleases();
void addSomeNonReleaseTracks();
+ static constexpr std::size_t _releasesBatchSize {6};
static constexpr std::size_t _tracksBatchSize {6};
static constexpr std::size_t _tracksMaxCount {160};
Filters* _filters {};
+ InfiniteScrollingContainer* _releaseContainer {};
InfiniteScrollingContainer* _trackContainer {};
Database::ArtistId _artistId {};
};
diff --git a/src/lms/ui/explore/ReleaseListHelpers.cpp b/src/lms/ui/explore/ReleaseListHelpers.cpp
index 296c4b42..cc5d2f41 100644
--- a/src/lms/ui/explore/ReleaseListHelpers.cpp
+++ b/src/lms/ui/explore/ReleaseListHelpers.cpp
@@ -83,8 +83,6 @@ namespace UserInterface::ReleaseListHelpers
}
}
- // hack
- entry->setStyleClass("p-2 Lms-bg-dark-hover rounded col-6 col-md-4 col-lg-3 col-xl-2");
return entry;
}