Added an optional role filter for 'appears on' section the artist views, fixes #824

This commit is contained in:
emeric
2026-03-14 16:15:07 +01:00
parent 14e18bf227
commit 535809f576
15 changed files with 436 additions and 262 deletions
+1
View File
@@ -49,6 +49,7 @@ add_executable(lms
ui/explore/ReleasesView.cpp
ui/explore/ReleaseView.cpp
ui/explore/ReleaseTypes.cpp
ui/explore/TrackArtistTypeSelector.cpp
ui/explore/TrackCollector.cpp
ui/explore/TrackListHelpers.cpp
ui/explore/TrackListView.cpp
+7 -7
View File
@@ -459,13 +459,13 @@ namespace lms::ui
_artistReleaseSortMethodModel->add(Wt::WString::tr("Lms.Settings.name"), db::ReleaseSortMethod::Name);
_artistRelationshipsModel = std::make_shared<ArtistRelationshipsModel>();
_artistRelationshipsModel->add(Wt::WString::trn("Lms.Explore.Artists.composer", 2), db::TrackArtistLinkType::Composer);
_artistRelationshipsModel->add(Wt::WString::trn("Lms.Explore.Artists.conductor", 2), db::TrackArtistLinkType::Conductor);
_artistRelationshipsModel->add(Wt::WString::trn("Lms.Explore.Artists.lyricist", 2), db::TrackArtistLinkType::Lyricist);
_artistRelationshipsModel->add(Wt::WString::trn("Lms.Explore.Artists.mixer", 2), db::TrackArtistLinkType::Mixer);
_artistRelationshipsModel->add(Wt::WString::trn("Lms.Explore.Artists.performer", 2), db::TrackArtistLinkType::Performer);
_artistRelationshipsModel->add(Wt::WString::trn("Lms.Explore.Artists.producer", 2), db::TrackArtistLinkType::Producer);
_artistRelationshipsModel->add(Wt::WString::trn("Lms.Explore.Artists.remixer", 2), db::TrackArtistLinkType::Remixer);
_artistRelationshipsModel->add(Wt::WString::trn("Lms.Explore.composer", 2), db::TrackArtistLinkType::Composer);
_artistRelationshipsModel->add(Wt::WString::trn("Lms.Explore.conductor", 2), db::TrackArtistLinkType::Conductor);
_artistRelationshipsModel->add(Wt::WString::trn("Lms.Explore.lyricist", 2), db::TrackArtistLinkType::Lyricist);
_artistRelationshipsModel->add(Wt::WString::trn("Lms.Explore.mixer", 2), db::TrackArtistLinkType::Mixer);
_artistRelationshipsModel->add(Wt::WString::trn("Lms.Explore.performer", 2), db::TrackArtistLinkType::Performer);
_artistRelationshipsModel->add(Wt::WString::trn("Lms.Explore.producer", 2), db::TrackArtistLinkType::Producer);
_artistRelationshipsModel->add(Wt::WString::trn("Lms.Explore.remixer", 2), db::TrackArtistLinkType::Remixer);
_transcodingModeModeModel = std::make_shared<TranscodingModeModel>();
_transcodingModeModeModel->add(Wt::WString::tr("Lms.Settings.transcoding-mode.always"), MediaPlayer::Settings::Transcoding::Mode::Always);
+14 -14
View File
@@ -407,17 +407,17 @@ namespace lms::ui::utils
});
};
addArtists(db::TrackArtistLinkType::Composer, "Lms.Explore.Artists.composer");
addArtists(db::TrackArtistLinkType::Conductor, "Lms.Explore.Artists.conductor");
addArtists(db::TrackArtistLinkType::Lyricist, "Lms.Explore.Artists.lyricist");
addArtists(db::TrackArtistLinkType::Mixer, "Lms.Explore.Artists.mixer");
addArtists(db::TrackArtistLinkType::Remixer, "Lms.Explore.Artists.remixer");
addArtists(db::TrackArtistLinkType::Producer, "Lms.Explore.Artists.producer");
addArtists(db::TrackArtistLinkType::Composer, "Lms.Explore.composer");
addArtists(db::TrackArtistLinkType::Conductor, "Lms.Explore.conductor");
addArtists(db::TrackArtistLinkType::Lyricist, "Lms.Explore.lyricist");
addArtists(db::TrackArtistLinkType::Mixer, "Lms.Explore.mixer");
addArtists(db::TrackArtistLinkType::Remixer, "Lms.Explore.remixer");
addArtists(db::TrackArtistLinkType::Producer, "Lms.Explore.producer");
addPerformerArtists();
if (!rolelessPerformers.empty())
{
Wt::WString performersStr{ Wt::WString::trn("Lms.Explore.Artists.performer", rolelessPerformers.size()) };
Wt::WString performersStr{ Wt::WString::trn("Lms.Explore.performer", rolelessPerformers.size()) };
artistMap[performersStr] = std::move(rolelessPerformers);
}
@@ -458,17 +458,17 @@ namespace lms::ui::utils
});
};
addArtists(db::TrackArtistLinkType::Composer, "Lms.Explore.Artists.composer");
addArtists(db::TrackArtistLinkType::Conductor, "Lms.Explore.Artists.conductor");
addArtists(db::TrackArtistLinkType::Lyricist, "Lms.Explore.Artists.lyricist");
addArtists(db::TrackArtistLinkType::Mixer, "Lms.Explore.Artists.mixer");
addArtists(db::TrackArtistLinkType::Remixer, "Lms.Explore.Artists.remixer");
addArtists(db::TrackArtistLinkType::Producer, "Lms.Explore.Artists.producer");
addArtists(db::TrackArtistLinkType::Composer, "Lms.Explore.composer");
addArtists(db::TrackArtistLinkType::Conductor, "Lms.Explore.conductor");
addArtists(db::TrackArtistLinkType::Lyricist, "Lms.Explore.lyricist");
addArtists(db::TrackArtistLinkType::Mixer, "Lms.Explore.mixer");
addArtists(db::TrackArtistLinkType::Remixer, "Lms.Explore.remixer");
addArtists(db::TrackArtistLinkType::Producer, "Lms.Explore.producer");
addPerformerArtists();
if (!rolelessPerformers.empty())
{
Wt::WString performersStr{ Wt::WString::trn("Lms.Explore.Artists.performer", rolelessPerformers.size()) };
Wt::WString performersStr{ Wt::WString::trn("Lms.Explore.performer", rolelessPerformers.size()) };
artistMap[performersStr] = std::move(rolelessPerformers);
}
+10 -10
View File
@@ -30,16 +30,16 @@ namespace lms::ui
ArtistTypeSelector(ArtistType defaultArtistType)
: DropDownMenuSelector{ Wt::WString::tr("Lms.Explore.Artists.template.artist-type-selector"), defaultArtistType }
{
bindItem("artist-type-all", Wt::WString::tr("Lms.Explore.Artists.all"), AllArtistsTag{});
bindItem("artist-type-release-artist", Wt::WString::trn("Lms.Explore.Artists.release-artist", 2), ReleaseArtistsTag{});
bindItem("artist-type-track-artist", Wt::WString::trn("Lms.Explore.Artists.track-artist", 2), db::TrackArtistLinkType::Artist);
bindItem("artist-type-composer", Wt::WString::trn("Lms.Explore.Artists.composer", 2), db::TrackArtistLinkType::Composer);
bindItem("artist-type-conductor", Wt::WString::trn("Lms.Explore.Artists.conductor", 2), db::TrackArtistLinkType::Conductor);
bindItem("artist-type-lyricist", Wt::WString::trn("Lms.Explore.Artists.lyricist", 2), db::TrackArtistLinkType::Lyricist);
bindItem("artist-type-mixer", Wt::WString::trn("Lms.Explore.Artists.mixer", 2), db::TrackArtistLinkType::Mixer);
bindItem("artist-type-performer", Wt::WString::trn("Lms.Explore.Artists.performer", 2), db::TrackArtistLinkType::Performer);
bindItem("artist-type-producer", Wt::WString::trn("Lms.Explore.Artists.producer", 2), db::TrackArtistLinkType::Producer);
bindItem("artist-type-remixer", Wt::WString::trn("Lms.Explore.Artists.remixer", 2), db::TrackArtistLinkType::Remixer);
bindItem("artist-type-all", Wt::WString::tr("Lms.Explore.all-artists"), AllArtistsTag{});
bindItem("artist-type-release-artist", Wt::WString::trn("Lms.Explore.release-artist", 2), ReleaseArtistsTag{});
bindItem("artist-type-track-artist", Wt::WString::trn("Lms.Explore.track-artist", 2), db::TrackArtistLinkType::Artist);
bindItem("artist-type-composer", Wt::WString::trn("Lms.Explore.composer", 2), db::TrackArtistLinkType::Composer);
bindItem("artist-type-conductor", Wt::WString::trn("Lms.Explore.conductor", 2), db::TrackArtistLinkType::Conductor);
bindItem("artist-type-lyricist", Wt::WString::trn("Lms.Explore.lyricist", 2), db::TrackArtistLinkType::Lyricist);
bindItem("artist-type-mixer", Wt::WString::trn("Lms.Explore.mixer", 2), db::TrackArtistLinkType::Mixer);
bindItem("artist-type-performer", Wt::WString::trn("Lms.Explore.performer", 2), db::TrackArtistLinkType::Performer);
bindItem("artist-type-producer", Wt::WString::trn("Lms.Explore.producer", 2), db::TrackArtistLinkType::Producer);
bindItem("artist-type-remixer", Wt::WString::trn("Lms.Explore.remixer", 2), db::TrackArtistLinkType::Remixer);
}
};
} // namespace lms::ui
+67 -14
View File
@@ -19,7 +19,10 @@
#include "ArtistView.hpp"
#include <array>
#include <Wt/WPushButton.h>
#include <Wt/WStackedWidget.h>
#include "core/String.hpp"
#include "database/Session.hpp"
@@ -30,6 +33,8 @@
#include "database/objects/Release.hpp"
#include "database/objects/ScanSettings.hpp"
#include "database/objects/Track.hpp"
#include "database/objects/TrackArtistLink.hpp"
#include "database/objects/Types.hpp"
#include "database/objects/User.hpp"
#include "services/feedback/IFeedbackService.hpp"
#include "services/recommendation/IRecommendationService.hpp"
@@ -40,6 +45,7 @@
#include "LmsApplicationException.hpp"
#include "PlayQueueController.hpp"
#include "ReleaseHelpers.hpp"
#include "TrackArtistTypeSelector.hpp"
#include "TrackListHelpers.hpp"
#include "Utils.hpp"
#include "common/InfiniteScrollingContainer.hpp"
@@ -272,30 +278,77 @@ namespace lms::ui
void Artist::refreshAppearsOnReleases()
{
_appearsOnReleases.clear();
_appearsOnReleaseContainer = {};
db::Release::FindParameters params;
params.setTrackArtist(_artistId);
params.setSortMethod(db::ReleaseSortMethod::OriginalDateDesc);
auto addToReleaseContainer = [&](std::optional<db::TrackArtistLinkType> linkType) -> bool {
db::Release::FindParameters params;
db::Release::find(LmsApp->getDbSession(), params, [this](const db::Release::pointer& release) {
if (!release->hasArtist(_artistId))
_appearsOnReleaseContainer.releases.push_back(release->getId());
});
core::EnumSet<db::TrackArtistLinkType> linkTypes;
if (linkType)
linkTypes.insert(*linkType);
if (!_appearsOnReleaseContainer.releases.empty())
params.setTrackArtist(_artistId, linkTypes);
params.setSortMethod(db::ReleaseSortMethod::OriginalDateDesc);
bool found{};
db::Release::find(LmsApp->getDbSession(), params, [&](const db::Release::pointer& release) {
if (release->hasArtist(_artistId))
return;
_appearsOnReleases[linkType].push_back(release->getId());
found = true;
});
return found;
};
constexpr std::array<std::optional<db::TrackArtistLinkType>, 9> types{
std::nullopt,
db::TrackArtistLinkType::Artist,
db::TrackArtistLinkType::Composer,
db::TrackArtistLinkType::Conductor,
db::TrackArtistLinkType::Lyricist,
db::TrackArtistLinkType::Mixer,
db::TrackArtistLinkType::Performer,
db::TrackArtistLinkType::Producer,
db::TrackArtistLinkType::Remixer,
};
core::EnumSet<db::TrackArtistLinkType> foundTypes;
for (const auto type : types)
{
Wt::WTemplate* releaseContainer{ bindNew<Wt::WTemplate>("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.container = releaseContainer->bindNew<InfiniteScrollingContainer>("releases", Wt::WString::tr("Lms.Explore.Releases.template.container"));
_appearsOnReleaseContainer.container->onRequestElements.connect(this, [this] {
if (addToReleaseContainer(type) && type)
foundTypes.insert(*type);
}
if (!_appearsOnReleases.empty())
{
std::optional<db::TrackArtistLinkType> defaultType{ std::nullopt };
if (_appearsOnReleases.size() == 2)
{
auto it{ std::find_if(std::cbegin(_appearsOnReleases), std::cend(_appearsOnReleases), [=](const auto& entry) { return entry.first != defaultType; }) };
if (it != std::cend(_appearsOnReleases))
defaultType = it->first;
}
Wt::WTemplate* appearsOnContainer{ bindNew<Wt::WTemplate>("appears-on-releases", Wt::WString::tr("Lms.Explore.Artist.template.appears-on-container")) };
appearsOnContainer->addFunction("tr", &Wt::WTemplate::Functions::tr);
_appearsOnReleaseContainer.container = appearsOnContainer->bindNew<InfiniteScrollingContainer>("releases", Wt::WString::tr("Lms.Explore.Releases.template.container"));
_appearsOnReleaseContainer.releases = _appearsOnReleases[defaultType];
_appearsOnReleaseContainer.container->onRequestElements.connect(this, [&] {
addSomeReleases(_appearsOnReleaseContainer, { releaseListHelpers::DisplayOptions::ShowYearAndOriginalYear, releaseListHelpers::DisplayOptions::ShowArtist });
});
TrackArtistTypeSelector* trackArtistTypeSelector{ appearsOnContainer->bindNew<TrackArtistTypeSelector>("artist-type", defaultType, foundTypes) };
trackArtistTypeSelector->itemSelected.connect([this](std::optional<db::TrackArtistLinkType> newArtistLinkType) {
_appearsOnReleaseContainer.releases = _appearsOnReleases[newArtistLinkType];
_appearsOnReleaseContainer.container->reset();
});
}
else
{
bindEmpty("appears-on-releases");
}
}
void Artist::refreshNonReleaseTracks()
+4 -1
View File
@@ -20,6 +20,7 @@
#pragma once
#include <map>
#include <optional>
#include "core/EnumSet.hpp"
@@ -31,6 +32,7 @@
#include "ReleaseHelpers.hpp"
#include "ReleaseTypes.hpp"
#include "common/Template.hpp"
#include "database/objects/Types.hpp"
namespace lms::db
{
@@ -76,7 +78,8 @@ namespace lms::ui
std::vector<db::ReleaseId> releases;
};
std::map<ReleaseType, ReleaseContainer> _releaseContainers;
ReleaseContainer _appearsOnReleaseContainer{};
std::map<std::optional<db::TrackArtistLinkType>, std::vector<db::ReleaseId>> _appearsOnReleases;
ReleaseContainer _appearsOnReleaseContainer;
InfiniteScrollingContainer* _trackContainer{};
db::ArtistId _artistId{};
std::string _artistName;
@@ -0,0 +1,60 @@
/*
* Copyright (C) 2026 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "TrackArtistTypeSelector.hpp"
#include <array>
namespace lms::ui
{
TrackArtistTypeSelector::TrackArtistTypeSelector(std::optional<db::TrackArtistLinkType> defaultRole, core::EnumSet<db::TrackArtistLinkType> types)
: DropDownMenuSelector{ Wt::WString::tr("Lms.Explore.Artist.template.track-artist-type-selector"), defaultRole }
{
struct Entry
{
std::string var;
Wt::WString title;
std::optional<db::TrackArtistLinkType> linkType;
core::LiteralString condition;
};
auto bindItemWithCondition = [&](const std::string& var, const Wt::WString& title, std::optional<db::TrackArtistLinkType> item, core::LiteralString condition) {
if (!condition.empty())
setCondition(condition.c_str(), true);
bindItem(var, title, item);
};
const std::array entries{
Entry{ "artist-type-all", Wt::WString::tr("Lms.Explore.all-roles"), std::nullopt, core::LiteralString{} },
Entry{ "artist-type-track-artist", Wt::WString::trn("Lms.Explore.track-artist", 1), db::TrackArtistLinkType::Artist, "if-has-track-artist" },
Entry{ "artist-type-composer", Wt::WString::trn("Lms.Explore.composer", 1), db::TrackArtistLinkType::Composer, "if-has-composer" },
Entry{ "artist-type-conductor", Wt::WString::trn("Lms.Explore.conductor", 1), db::TrackArtistLinkType::Conductor, "if-has-conductor" },
Entry{ "artist-type-lyricist", Wt::WString::trn("Lms.Explore.lyricist", 1), db::TrackArtistLinkType::Lyricist, "if-has-lyricist" },
Entry{ "artist-type-mixer", Wt::WString::trn("Lms.Explore.mixer", 1), db::TrackArtistLinkType::Mixer, "if-has-mixer" },
Entry{ "artist-type-performer", Wt::WString::trn("Lms.Explore.performer", 1), db::TrackArtistLinkType::Performer, "if-has-performer" },
Entry{ "artist-type-producer", Wt::WString::trn("Lms.Explore.producer", 1), db::TrackArtistLinkType::Producer, "if-has-producer" },
Entry{ "artist-type-remixer", Wt::WString::trn("Lms.Explore.remixer", 1), db::TrackArtistLinkType::Remixer, "if-has-remixer" },
};
for (const Entry& entry : entries)
{
if (!entry.linkType || types.contains(*entry.linkType))
bindItemWithCondition(entry.var, entry.title, entry.linkType, entry.condition);
}
}
} // namespace lms::ui
@@ -0,0 +1,35 @@
/*
* Copyright (C) 2026 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "core/EnumSet.hpp"
#include "database/objects/Types.hpp"
#include "DropDownMenuSelector.hpp"
namespace lms::ui
{
class TrackArtistTypeSelector : public DropDownMenuSelector<std::optional<db::TrackArtistLinkType>>
{
public:
TrackArtistTypeSelector(std::optional<db::TrackArtistLinkType> defaultRole, core::EnumSet<db::TrackArtistLinkType> types);
};
} // namespace lms::ui