diff --git a/approot/artist.xml b/approot/artist.xml index add9a9e7..3b305bb0 100644 --- a/approot/artist.xml +++ b/approot/artist.xml @@ -20,14 +20,8 @@ - ${} - ${}

${tr:Lms.Explore.releases}

${
} - ${releases class="mb-3"} - ${
} - ${} -

${tr:Lms.Explore.Artist.appears-on}

- ${appears-on-releases class="mb-3"} - ${
} + ${release-containers} + ${appears-on-releases} ${}

${tr:Lms.Explore.tracks}

${tracks class="mb-3"} @@ -38,4 +32,11 @@ ${} + +
+

${release-type}

+ ${releases} +
+
+ diff --git a/src/libs/services/database/impl/Release.cpp b/src/libs/services/database/impl/Release.cpp index 93b6dfd3..d26bfc40 100644 --- a/src/libs/services/database/impl/Release.cpp +++ b/src/libs/services/database/impl/Release.cpp @@ -139,6 +139,11 @@ createQuery(Session& session, const Release::FindParameters& params) query.where(oss.str()); } + if (params.primaryType) + query.where("primary_type = ?").bind(*params.primaryType); + if (!params.secondaryTypes.empty()) + query.where("secondary_type = ?").bind(params.secondaryTypes); + switch (params.sortMethod) { case ReleaseSortMethod::None: diff --git a/src/libs/services/database/include/services/database/Release.hpp b/src/libs/services/database/include/services/database/Release.hpp index f985a448..3f2957d4 100644 --- a/src/libs/services/database/include/services/database/Release.hpp +++ b/src/libs/services/database/include/services/database/Release.hpp @@ -61,6 +61,8 @@ class Release : public Object ArtistId artist; // only releases that involved this user EnumSet trackArtistLinkTypes; // and for these link types EnumSet excludedTrackArtistLinkTypes; // but not for these link types + std::optional primaryType; // if, set, matching this primary type + EnumSet secondaryTypes; // Matching all this (if any) FindParameters& setClusters(const std::vector& _clusters) { clusters = _clusters; return *this; } FindParameters& setKeywords(const std::vector& _keywords) { keywords = _keywords; return *this; } diff --git a/src/lms/CMakeLists.txt b/src/lms/CMakeLists.txt index 7929bd44..482247fa 100644 --- a/src/lms/CMakeLists.txt +++ b/src/lms/CMakeLists.txt @@ -34,7 +34,7 @@ add_executable(lms ui/explore/Filters.cpp ui/explore/PlayQueueController.cpp ui/explore/ReleaseCollector.cpp - ui/explore/ReleaseListHelpers.cpp + ui/explore/ReleaseHelpers.cpp ui/explore/ReleasesView.cpp ui/explore/ReleaseView.cpp ui/explore/SearchView.cpp diff --git a/src/lms/ui/explore/ArtistView.cpp b/src/lms/ui/explore/ArtistView.cpp index a8ce5f74..95d551c5 100644 --- a/src/lms/ui/explore/ArtistView.cpp +++ b/src/lms/ui/explore/ArtistView.cpp @@ -40,7 +40,7 @@ #include "LmsApplication.hpp" #include "LmsApplicationException.hpp" #include "PlayQueueController.hpp" -#include "ReleaseListHelpers.hpp" +#include "ReleaseHelpers.hpp" #include "TrackListHelpers.hpp" #include "Utils.hpp" @@ -48,6 +48,19 @@ using namespace Database; namespace UserInterface { +bool +Artist::ReleaseType::operator<(const ReleaseType& other) const +{ + if (!primaryType && other.primaryType) + return false; + else if (primaryType && !other.primaryType) + return true; + else if (*primaryType == *other.primaryType) + return secondaryTypes.getBitfield() < other.secondaryTypes.getBitfield(); + else + return static_cast(*primaryType) < static_cast(*other.primaryType); +} + Artist::Artist(Filters& filters, PlayQueueController& controller) : Template {Wt::WString::tr("Lms.Explore.Artist.template")} , _filters {filters} @@ -114,15 +127,12 @@ Artist::refreshView() LmsApp->setTitle(artist->getName()); _artistId = *artistId; - bool setReleaseTitle {}; refreshReleases(); - setReleaseTitle |= refreshAppearsOnReleases(); - setReleaseTitle |= refreshNonReleaseTracks(); + refreshAppearsOnReleases(); + refreshNonReleaseTracks(); refreshLinks(artist); refreshSimilarArtists(similarArtistIds); - setCondition("if-release-title", setReleaseTitle); - Wt::WContainerWidget* clusterContainers {bindNew("clusters")}; { @@ -184,25 +194,57 @@ Artist::refreshView() } } -bool +void Artist::refreshReleases() { - _releaseContainer = bindNew("releases", Wt::WString::tr("Lms.Explore.Releases.template.container")); - _releaseContainer->onRequestElements.connect(this, [this] + _releaseContainers.clear(); + + Release::FindParameters params; + params.setClusters(_filters.getClusterIds()); + params.setArtist(_artistId, {TrackArtistLinkType::ReleaseArtist}, {}); + params.setSortMethod(ReleaseSortMethod::DateDesc); + + const auto releases {Release::find(LmsApp->getDbSession(), params)}; + if (!releases.results.empty()) { - addSomeReleases(*_releaseContainer, {TrackArtistLinkType::ReleaseArtist}, {}); - }); + // first pass: gather all ids and sort by type + for (const ReleaseId releaseId : releases.results) + { + const Database::Release::pointer release {Database::Release::find(LmsApp->getDbSession(), releaseId)}; - const bool added {addSomeReleases(*_releaseContainer, {TrackArtistLinkType::ReleaseArtist}, {})}; - setCondition("if-has-releases", added); + ReleaseType releaseType {release->getPrimaryType(), release->getSecondaryTypes()}; + _releaseContainers[releaseType].releases.push_back(releaseId); + } - return added; + // second pass: construct widgets + Wt::WContainerWidget* releaseContainers {bindNew("release-containers")}; + for (auto& [releaseType, releases] : _releaseContainers) + { + Wt::WTemplate* releaseContainer {releaseContainers->addNew(Wt::WString::tr("Lms.Explore.Artist.template.release-container"))}; + + if (releaseType.primaryType) + releaseContainer->bindString("release-type", ReleaseHelpers::buildReleaseTypeString(*releaseType.primaryType, releaseType.secondaryTypes)); + else + releaseContainer->bindString("release-type", Wt::WString::tr("Lms.Explore.releases")); // fallback when not tagged with MB + + releases.container = releaseContainer->bindNew("releases", Wt::WString::tr("Lms.Explore.Releases.template.container")); + releases.container->onRequestElements.connect(this, [this, &releases] + { + addSomeReleases(releases); + }); + releases.container->setHasMore(true); + } + } + else + { + bindEmpty("release-containers"); + } } -bool +void Artist::refreshAppearsOnReleases() { - const EnumSet types + constexpr EnumSet types { TrackArtistLinkType::Artist, TrackArtistLinkType::Arranger, @@ -216,18 +258,33 @@ Artist::refreshAppearsOnReleases() TrackArtistLinkType::Writer, }; - _appearsOnReleaseContainer = bindNew("appears-on-releases", Wt::WString::tr("Lms.Explore.Releases.template.container")); - _appearsOnReleaseContainer->onRequestElements.connect(this, [=] - { - addSomeReleases(*_appearsOnReleaseContainer, types, {TrackArtistLinkType::ReleaseArtist}); - }); + _appearsOnReleaseContainer = {}; - const bool added {addSomeReleases(*_appearsOnReleaseContainer, types, {TrackArtistLinkType::ReleaseArtist})}; - setCondition("if-has-appears-on-releases", added); - return added; + Release::FindParameters params; + params.setClusters(_filters.getClusterIds()); + params.setArtist(_artistId, types, {TrackArtistLinkType::ReleaseArtist}); + params.setSortMethod(ReleaseSortMethod::DateDesc); + + const auto releases {Release::find(LmsApp->getDbSession(), params)}; + if (!releases.results.empty()) + { + Wt::WTemplate* releaseContainer {bindNew("appears-on-releases", Wt::WString::tr("Lms.Explore.Artist.template.release-container"))}; + releaseContainer->bindString("release-type", Wt::WString::tr("Lms.Explore.Artist.appears-on")); + _appearsOnReleaseContainer.releases = releases.results; + _appearsOnReleaseContainer.container = releaseContainer->bindNew("releases", Wt::WString::tr("Lms.Explore.Releases.template.container")); + _appearsOnReleaseContainer.container->onRequestElements.connect(this, [this, &releases] + { + addSomeReleases(_appearsOnReleaseContainer); + }); + _appearsOnReleaseContainer.container->setHasMore(true); + } + else + { + bindEmpty("appears-on-releases"); + } } -bool +void Artist::refreshNonReleaseTracks() { setCondition("if-has-non-release-tracks", true); @@ -239,7 +296,6 @@ Artist::refreshNonReleaseTracks() const bool added {addSomeNonReleaseTracks()}; setCondition("if-has-non-release-tracks", added); - return added; } void @@ -272,35 +328,24 @@ Artist::refreshLinks(const Database::Artist::pointer& artist) } } -bool -Artist::addSomeReleases(InfiniteScrollingContainer& releaseContainer, EnumSet linkTypes, EnumSet excludedLinkTypes) +void +Artist::addSomeReleases(ReleaseContainer& releaseContainer) { - bool areArtistsAdded{}; auto transaction {LmsApp->getDbSession().createSharedTransaction()}; - const Database::Artist::pointer artist {Database::Artist::find(LmsApp->getDbSession(), _artistId)}; - if (!artist) - return areArtistsAdded; - - const Range range {static_cast(releaseContainer.getCount()), _releasesBatchSize}; - - Release::FindParameters params; - params.setClusters(_filters.getClusterIds()); - params.setArtist(_artistId, linkTypes, excludedLinkTypes); - params.setRange(range); - params.setSortMethod(ReleaseSortMethod::DateDesc); - - const auto releases {Release::find(LmsApp->getDbSession(), params)}; - for (const ReleaseId releaseId : releases.results) + if (const Database::Artist::pointer artist {Database::Artist::find(LmsApp->getDbSession(), _artistId)}) { - const Database::Release::pointer release {Database::Release::find(LmsApp->getDbSession(), releaseId)}; - releaseContainer.add(ReleaseListHelpers::createEntryForArtist(release, artist)); - areArtistsAdded = true; + for (std::size_t i {static_cast(releaseContainer.container->getCount())}; i < releaseContainer.releases.size(); ++i) + { + const Database::Release::pointer release {Database::Release::find(LmsApp->getDbSession(), releaseContainer.releases[i])}; + releaseContainer.container->add(ReleaseListHelpers::createEntryForArtist(release, artist)); + } + releaseContainer.container->setHasMore(releaseContainer.container->getCount() < releaseContainer.releases.size()); + } + else + { + releaseContainer.container->setHasMore(false); } - - releaseContainer.setHasMore(releases.moreResults); - - return areArtistsAdded; } bool diff --git a/src/lms/ui/explore/ArtistView.hpp b/src/lms/ui/explore/ArtistView.hpp index d583a893..f2314668 100644 --- a/src/lms/ui/explore/ArtistView.hpp +++ b/src/lms/ui/explore/ArtistView.hpp @@ -19,8 +19,10 @@ #pragma once +#include #include "services/database/ArtistId.hpp" #include "services/database/Object.hpp" +#include "services/database/ReleaseId.hpp" #include "services/database/Types.hpp" #include "utils/EnumSet.hpp" #include "common/Template.hpp" @@ -44,13 +46,14 @@ namespace UserInterface private: void refreshView(); - bool refreshReleases(); - bool refreshAppearsOnReleases(); - bool refreshNonReleaseTracks(); + void refreshReleases(); + void refreshAppearsOnReleases(); + void refreshNonReleaseTracks(); void refreshSimilarArtists(const std::vector& similarArtistsId); void refreshLinks(const Database::ObjectPtr& artist); - bool addSomeReleases(InfiniteScrollingContainer& container, EnumSet linkTypes, EnumSet excludedLinkTypes); + struct ReleaseContainer; + void addSomeReleases(ReleaseContainer& releaseContainer); bool addSomeNonReleaseTracks(); static constexpr std::size_t _releasesBatchSize {6}; static constexpr std::size_t _tracksBatchSize {6}; @@ -58,8 +61,22 @@ namespace UserInterface Filters& _filters; PlayQueueController& _playQueueController; - InfiniteScrollingContainer* _releaseContainer {}; - InfiniteScrollingContainer* _appearsOnReleaseContainer {}; + + struct ReleaseType + { + std::optional primaryType; + EnumSet secondaryTypes; + + bool operator<(const ReleaseType& other) const; + }; + + struct ReleaseContainer + { + InfiniteScrollingContainer* container {}; + std::vector releases; + }; + std::map _releaseContainers; + ReleaseContainer _appearsOnReleaseContainer {}; InfiniteScrollingContainer* _trackContainer {}; Database::ArtistId _artistId {}; }; diff --git a/src/lms/ui/explore/ReleaseListHelpers.cpp b/src/lms/ui/explore/ReleaseHelpers.cpp similarity index 57% rename from src/lms/ui/explore/ReleaseListHelpers.cpp rename to src/lms/ui/explore/ReleaseHelpers.cpp index dc69fa88..a1db7549 100644 --- a/src/lms/ui/explore/ReleaseListHelpers.cpp +++ b/src/lms/ui/explore/ReleaseHelpers.cpp @@ -17,7 +17,7 @@ * along with LMS. If not, see . */ -#include "ReleaseListHelpers.hpp" +#include "ReleaseHelpers.hpp" #include #include @@ -104,3 +104,42 @@ namespace UserInterface::ReleaseListHelpers } } // namespace UserInterface +namespace UserInterface::ReleaseHelpers +{ + Wt::WString + buildReleaseTypeString(ReleaseTypePrimary primaryType, EnumSet secondaryTypes) + { + Wt::WString res; + + switch (primaryType) + { + case ReleaseTypePrimary::Album: res = Wt::WString::tr("Lms.Explore.Release.type-primary-album"); break; + case ReleaseTypePrimary::Broadcast: res = Wt::WString::tr("Lms.Explore.Release.type-primary-broadcast"); break; + case ReleaseTypePrimary::EP: res = Wt::WString::tr("Lms.Explore.Release.type-primary-ep"); break; + case ReleaseTypePrimary::Single: res = Wt::WString::tr("Lms.Explore.Release.type-primary-single"); break; + case ReleaseTypePrimary::Other: res = Wt::WString::tr("Lms.Explore.Release.type-primary-other"); break; + } + + for (ReleaseTypeSecondary secondaryType : secondaryTypes) + { + res += Wt::WString {" · "}; + + switch (secondaryType) + { + case ReleaseTypeSecondary::Compilation: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-compilation"); break; + case ReleaseTypeSecondary::Spokenword: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-spokenword"); break; + case ReleaseTypeSecondary::Soundtrack: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-soundtrack"); break; + case ReleaseTypeSecondary::Interview: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-interview"); break; + case ReleaseTypeSecondary::Audiobook: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-audiobook"); break; + case ReleaseTypeSecondary::AudioDrama: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-audiodrama"); break; + case ReleaseTypeSecondary::Live: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-live"); break; + case ReleaseTypeSecondary::Remix: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-remix"); break; + case ReleaseTypeSecondary::DJMix: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-djmix"); break; + case ReleaseTypeSecondary::Mixtape_Street: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-mixtape-street"); break; + case ReleaseTypeSecondary::Demo: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-demo"); break; + } + } + + return res; + } +} // namespace UserInterface::ReleaseHelpers} diff --git a/src/lms/ui/explore/ReleaseListHelpers.hpp b/src/lms/ui/explore/ReleaseHelpers.hpp similarity index 82% rename from src/lms/ui/explore/ReleaseListHelpers.hpp rename to src/lms/ui/explore/ReleaseHelpers.hpp index 41550b85..b0c61911 100644 --- a/src/lms/ui/explore/ReleaseListHelpers.hpp +++ b/src/lms/ui/explore/ReleaseHelpers.hpp @@ -23,6 +23,8 @@ #include #include "services/database/Object.hpp" +#include "services/database/Types.hpp" +#include "utils/EnumSet.hpp" namespace Database { @@ -36,3 +38,7 @@ namespace UserInterface::ReleaseListHelpers std::unique_ptr createEntryForArtist(const Database::ObjectPtr& release, const Database::ObjectPtr& artist); } // namespace UserInterface +namespace UserInterface::ReleaseHelpers +{ + Wt::WString buildReleaseTypeString(Database::ReleaseTypePrimary primaryType, EnumSet secondaryTypes); +} diff --git a/src/lms/ui/explore/ReleaseView.cpp b/src/lms/ui/explore/ReleaseView.cpp index 194410e7..64b1bbb8 100644 --- a/src/lms/ui/explore/ReleaseView.cpp +++ b/src/lms/ui/explore/ReleaseView.cpp @@ -40,7 +40,7 @@ #include "resource/DownloadResource.hpp" #include "explore/Filters.hpp" #include "explore/PlayQueueController.hpp" -#include "explore/ReleaseListHelpers.hpp" +#include "explore/ReleaseHelpers.hpp" #include "explore/TrackListHelpers.hpp" #include "LmsApplication.hpp" #include "LmsApplicationException.hpp" @@ -52,43 +52,6 @@ using namespace Database; namespace UserInterface { -Wt::WString -buildReleaseTypeString(ReleaseTypePrimary primaryType, EnumSet secondaryTypes) -{ - Wt::WString res; - - switch (primaryType) - { - case ReleaseTypePrimary::Album: res = Wt::WString::tr("Lms.Explore.Release.type-primary-album"); break; - case ReleaseTypePrimary::Broadcast: res = Wt::WString::tr("Lms.Explore.Release.type-primary-broadcast"); break; - case ReleaseTypePrimary::EP: res = Wt::WString::tr("Lms.Explore.Release.type-primary-ep"); break; - case ReleaseTypePrimary::Single: res = Wt::WString::tr("Lms.Explore.Release.type-primary-single"); break; - case ReleaseTypePrimary::Other: res = Wt::WString::tr("Lms.Explore.Release.type-primary-other"); break; - } - - for (ReleaseTypeSecondary secondaryType : secondaryTypes) - { - res += Wt::WString {" · "}; - - switch (secondaryType) - { - case ReleaseTypeSecondary::Compilation: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-compilation"); break; - case ReleaseTypeSecondary::Spokenword: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-spokenword"); break; - case ReleaseTypeSecondary::Soundtrack: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-soundtrack"); break; - case ReleaseTypeSecondary::Interview: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-interview"); break; - case ReleaseTypeSecondary::Audiobook: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-audiobook"); break; - case ReleaseTypeSecondary::AudioDrama: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-audiodrama"); break; - case ReleaseTypeSecondary::Live: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-live"); break; - case ReleaseTypeSecondary::Remix: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-remix"); break; - case ReleaseTypeSecondary::DJMix: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-djmix"); break; - case ReleaseTypeSecondary::Mixtape_Street: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-mixtape-street"); break; - case ReleaseTypeSecondary::Demo: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-demo"); break; - } - } - - return res; -} - void showReleaseInfoModal(Database::ReleaseId releaseId) { @@ -105,7 +68,7 @@ showReleaseInfoModal(Database::ReleaseId releaseId) if (auto primaryReleaseType {release->getPrimaryType()}) { releaseInfo->setCondition("if-has-release-type", true); - releaseInfo->bindString("release-type", buildReleaseTypeString(*primaryReleaseType, release->getSecondaryTypes())); + releaseInfo->bindString("release-type", ReleaseHelpers::buildReleaseTypeString(*primaryReleaseType, release->getSecondaryTypes())); } std::map> artistMap; diff --git a/src/lms/ui/explore/ReleasesView.cpp b/src/lms/ui/explore/ReleasesView.cpp index 4d3cd927..883ba936 100644 --- a/src/lms/ui/explore/ReleasesView.cpp +++ b/src/lms/ui/explore/ReleasesView.cpp @@ -28,7 +28,7 @@ #include "common/Template.hpp" #include "explore/Filters.hpp" #include "explore/PlayQueueController.hpp" -#include "explore/ReleaseListHelpers.hpp" +#include "explore/ReleaseHelpers.hpp" #include "LmsApplication.hpp" using namespace Database; diff --git a/src/lms/ui/explore/SearchView.cpp b/src/lms/ui/explore/SearchView.cpp index 92d0df1f..4ff459c6 100644 --- a/src/lms/ui/explore/SearchView.cpp +++ b/src/lms/ui/explore/SearchView.cpp @@ -28,7 +28,7 @@ #include "ArtistListHelpers.hpp" #include "Filters.hpp" #include "LmsApplication.hpp" -#include "ReleaseListHelpers.hpp" +#include "ReleaseHelpers.hpp" #include "TrackListHelpers.hpp" using namespace Database;