diff --git a/approot/messages.xml b/approot/messages.xml
index c82129b1..31f6b7ae 100644
--- a/approot/messages.xml
+++ b/approot/messages.xml
@@ -309,8 +309,10 @@
Default transcoding output bitrate
Default transcoding output format
Cannot save using a demo account!
+Display artist relationships inline within albums
Enable transcoding by default
Feedback
+Types of artist relationships to display
Album Name
Original Release Date (Ascending)
diff --git a/approot/messages_fr.xml b/approot/messages_fr.xml
index 8083fc56..bbcb1be8 100644
--- a/approot/messages_fr.xml
+++ b/approot/messages_fr.xml
@@ -309,8 +309,10 @@
Bitrate par défaut du transcodage
Format par défaut du transcodage
Impossible de sauvegarder en utilisant un compte de démo !
+Afficher les relations d'artiste en ligne dans les albums
Activer par défaut le transcodage
Feedback
+Types de relations d'artiste à afficher
Nom de l'album
Date de sortie originale (Croissant)
diff --git a/approot/messages_it.xml b/approot/messages_it.xml
index 567097ab..ae9553e2 100644
--- a/approot/messages_it.xml
+++ b/approot/messages_it.xml
@@ -309,8 +309,10 @@
Bitrate di default per la transcodifica
Formato di default per la transcodifica
Non posso salvare le impostazioni utilizzando l'account demo!
+Mostra le relazioni degli artisti in linea negli album
Abilita transcodifica di default
Feedback
+Tipi di relazioni artistiche da visualizzare
Nome dell'album
Data di uscita originale (Crescente)
diff --git a/approot/messages_pl.xml b/approot/messages_pl.xml
index ac97014a..286be68a 100644
--- a/approot/messages_pl.xml
+++ b/approot/messages_pl.xml
@@ -335,8 +335,10 @@
Domyślna przepływność transkodowania
Domyślny format wyjściowy transkodowania
Nie można zapisywać na koncie demonstracyjnym!
+Wyświetlaj powiązania artystów w albumach
Aktywuj domyślnie transkodowanie
Feedback
+Typy powiązań artystów do wyświetlenia
Nazwa albumu
Oryginalna data wydania (Rosnąco)
diff --git a/approot/messages_zh.xml b/approot/messages_zh.xml
index 21e240fa..3e892392 100644
--- a/approot/messages_zh.xml
+++ b/approot/messages_zh.xml
@@ -311,6 +311,8 @@
使用演示账号时不能保存!
+
+
diff --git a/approot/settings.xml b/approot/settings.xml
index 54644c47..46a4bafa 100644
--- a/approot/settings.xml
+++ b/approot/settings.xml
@@ -15,6 +15,26 @@
${artist-release-sort-method-info}
+
+
+
+ ${tr:Lms.Settings.inline-artist-relationships}
+
+ ${inline-artist-relationships class="form-control"}
+
+ ${inline-artist-relationships-info}
+
+
${tr:Lms.Settings.audio}
diff --git a/src/libs/database/impl/Migration.cpp b/src/libs/database/impl/Migration.cpp
index 08c8d31d..31322d8e 100644
--- a/src/libs/database/impl/Migration.cpp
+++ b/src/libs/database/impl/Migration.cpp
@@ -35,7 +35,7 @@ namespace lms::db
{
namespace
{
- static constexpr Version LMS_DATABASE_VERSION{ 82 };
+ static constexpr Version LMS_DATABASE_VERSION{ 83 };
}
VersionInfo::VersionInfo()
@@ -1099,6 +1099,13 @@ FROM tracklist)");
utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1");
}
+ void migrateFromV82(Session& session)
+ {
+ // new setting to display inline artist relationships in the release view
+ utils::executeCommand(*session.getDboSession(), "ALTER TABLE user ADD COLUMN ui_enable_inline_artist_relationships BOOLEAN NOT NULL DEFAULT(false)");
+ utils::executeCommand(*session.getDboSession(), "ALTER TABLE user ADD COLUMN ui_inline_artist_relationships BIGINT NOT NULL DEFAULT(68)"); // Composer + Performer
+ }
+
bool doDbMigration(Session& session)
{
constexpr std::string_view outdatedMsg{ "Outdated database, please rebuild it (delete the .db file and restart)" };
@@ -1157,6 +1164,7 @@ FROM tracklist)");
{ 79, migrateFromV79 },
{ 80, migrateFromV80 },
{ 81, migrateFromV81 },
+ { 82, migrateFromV82 },
};
bool migrationPerformed{};
diff --git a/src/libs/database/impl/User.cpp b/src/libs/database/impl/User.cpp
index ef852783..c5f8d06e 100644
--- a/src/libs/database/impl/User.cpp
+++ b/src/libs/database/impl/User.cpp
@@ -19,13 +19,15 @@
#include "database/User.hpp"
-#include "core/ILogger.hpp"
+#include
+
#include "database/Artist.hpp"
#include "database/Release.hpp"
#include "database/Session.hpp"
#include "database/Track.hpp"
#include "database/UIState.hpp"
+#include "EnumSetTraits.hpp"
#include "IdTypeTraits.hpp"
#include "StringViewTraits.hpp"
#include "Utils.hpp"
diff --git a/src/libs/database/include/database/User.hpp b/src/libs/database/include/database/User.hpp
index 0ec076b2..4bf2c4fc 100644
--- a/src/libs/database/include/database/User.hpp
+++ b/src/libs/database/include/database/User.hpp
@@ -21,11 +21,11 @@
#include
#include
-#include
#include
#include
+#include "core/EnumSet.hpp"
#include "core/UUID.hpp"
#include "database/Object.hpp"
#include "database/Types.hpp"
@@ -110,6 +110,8 @@ namespace lms::db
void setSubsonicDefaultTranscodingOutputBitrate(Bitrate bitrate);
void setUITheme(UITheme uiTheme) { _uiTheme = uiTheme; }
void setUIArtistReleaseSortMethod(ReleaseSortMethod method) { _uiArtistReleaseSortMethod = method; }
+ void setUIEnableInlineArtistRelationships(bool enable) { _uiEnableInlineArtistRelationships = enable; }
+ void setUIInlineArtistRelationships(core::EnumSet types) { _uiInlineArtistRelationships = types; }
void setSubsonicArtistListMode(SubsonicArtistListMode mode) { _subsonicArtistListMode = mode; }
void setFeedbackBackend(FeedbackBackend feedbackBackend) { _feedbackBackend = feedbackBackend; }
void setScrobblingBackend(ScrobblingBackend scrobblingBackend) { _scrobblingBackend = scrobblingBackend; }
@@ -124,6 +126,8 @@ namespace lms::db
Bitrate getSubsonicDefaultTranscodingOutputBitrate() const { return _subsonicDefaultTranscodingOutputBitrate; }
UITheme getUITheme() const { return _uiTheme; }
ReleaseSortMethod getUIArtistReleaseSortMethod() const { return _uiArtistReleaseSortMethod; }
+ bool getUIEnableInlineArtistRelationships() const { return _uiEnableInlineArtistRelationships; }
+ core::EnumSet getUIInlineArtistRelationships() const { return _uiInlineArtistRelationships; }
SubsonicArtistListMode getSubsonicArtistListMode() const { return _subsonicArtistListMode; }
FeedbackBackend getFeedbackBackend() const { return _feedbackBackend; }
ScrobblingBackend getScrobblingBackend() const { return _scrobblingBackend; }
@@ -144,6 +148,8 @@ namespace lms::db
Wt::Dbo::field(a, _subsonicArtistListMode, "subsonic_artist_list_mode");
Wt::Dbo::field(a, _uiTheme, "ui_theme");
Wt::Dbo::field(a, _uiArtistReleaseSortMethod, "ui_artist_release_sort_method");
+ Wt::Dbo::field(a, _uiEnableInlineArtistRelationships, "ui_enable_inline_artist_relationships");
+ Wt::Dbo::field(a, _uiInlineArtistRelationships, "ui_inline_artist_relationships");
Wt::Dbo::field(a, _feedbackBackend, "feedback_backend");
Wt::Dbo::field(a, _scrobblingBackend, "scrobbling_backend");
Wt::Dbo::field(a, _listenbrainzToken, "listenbrainz_token");
@@ -164,6 +170,8 @@ namespace lms::db
Wt::WDateTime _lastLogin;
UITheme _uiTheme{ defaultUITheme };
ReleaseSortMethod _uiArtistReleaseSortMethod{ _defaultUIArtistReleaseSortMethod };
+ bool _uiEnableInlineArtistRelationships{};
+ core::EnumSet _uiInlineArtistRelationships{ TrackArtistLinkType::Composer, TrackArtistLinkType::Performer };
FeedbackBackend _feedbackBackend{ defaultFeedbackBackend };
ScrobblingBackend _scrobblingBackend{ defaultScrobblingBackend };
std::string _listenbrainzToken; // Musicbrainz Identifier
diff --git a/src/lms/ui/SettingsView.cpp b/src/lms/ui/SettingsView.cpp
index befccef5..6507d5aa 100644
--- a/src/lms/ui/SettingsView.cpp
+++ b/src/lms/ui/SettingsView.cpp
@@ -25,12 +25,15 @@
#include
#include
#include
+#include
#include
#include
#include "core/IConfig.hpp"
#include "core/Service.hpp"
+#include "core/String.hpp"
#include "database/Session.hpp"
+#include "database/Types.hpp"
#include "database/User.hpp"
#include "services/auth/IAuthTokenService.hpp"
#include "services/auth/IPasswordService.hpp"
@@ -46,6 +49,44 @@
namespace lms::ui
{
+ namespace
+ {
+ // highly unefficient hack to make WSelectionBox work with Wt::WFormModel
+ class SelectionBox : public Wt::WSelectionBox
+ {
+ public:
+ static inline constexpr std::string_view valueSeparator{ ", " };
+
+ private:
+ void setValueText(const Wt::WString& values) override
+ {
+ std::set selectedIndexes;
+ const std::string strValues{ values.toUTF8() };
+ for (std::string_view value : core::stringUtils::splitString(strValues, valueSeparator))
+ {
+ const int index{ findText(std::string{ value }) };
+ if (index >= 0)
+ selectedIndexes.insert(index);
+ }
+
+ setSelectedIndexes(selectedIndexes);
+ }
+
+ Wt::WString valueText() const override
+ {
+ Wt::WString res;
+ for (int index : selectedIndexes())
+ {
+ if (!res.empty())
+ res += std::string{ valueSeparator };
+ res += itemText(index);
+ }
+
+ return res;
+ }
+ };
+ } // namespace
+
using namespace db;
class SettingsModel : public Wt::WFormModel
@@ -53,6 +94,8 @@ namespace lms::ui
public:
// Associate each field with a unique string literal.
static inline const Field ArtistReleaseSortMethodField{ "artist-release-sort-method" };
+ static inline const Field EnableInlineArtistRelationships{ "enable-inline-artist-relationships" };
+ static inline const Field InlineArtistRelationships{ "inline-artist-relationships" };
static inline const Field TranscodingModeField{ "transcoding-mode" };
static inline const Field TranscodeFormatField{ "transcoding-output-format" };
static inline const Field TranscodeBitrateField{ "transcoding-output-bitrate" };
@@ -72,6 +115,7 @@ namespace lms::ui
static inline const Field PasswordConfirmField{ "password-confirm" };
using ArtistReleaseSortMethodModel = ValueStringModel;
+ using ArtistRelationshipsModel = ValueStringModel;
using TranscodingModeModel = ValueStringModel;
using ReplayGainModeModel = ValueStringModel;
using FeedbackBackendModel = ValueStringModel;
@@ -85,6 +129,8 @@ namespace lms::ui
initializeModels();
addField(ArtistReleaseSortMethodField);
+ addField(EnableInlineArtistRelationships);
+ addField(InlineArtistRelationships);
addField(TranscodingModeField);
addField(TranscodeBitrateField);
addField(TranscodeFormatField);
@@ -133,6 +179,7 @@ namespace lms::ui
}
std::shared_ptr getArtistReleaseSortMethodModel() { return _artistReleaseSortMethodModel; }
+ std::shared_ptr getArtistRelationshipsModel() { return _artistRelationshipsModel; }
std::shared_ptr getTranscodingModeModel() { return _transcodingModeModeModel; }
std::shared_ptr getTranscodingOutputBitrateModel() { return _transcodingOutputBitrateModel; }
std::shared_ptr getTranscodingOutputFormatModel() { return _transcodingOutputFormatModel; }
@@ -148,9 +195,23 @@ namespace lms::ui
User::pointer user{ LmsApp->getUser() };
{
- auto artistReleaseSortMethodRow{ _artistReleaseSortMethodModel->getRowFromString(valueText(ArtistReleaseSortMethodField)) };
+ const auto artistReleaseSortMethodRow{ _artistReleaseSortMethodModel->getRowFromString(valueText(ArtistReleaseSortMethodField)) };
if (artistReleaseSortMethodRow)
user.modify()->setUIArtistReleaseSortMethod(_artistReleaseSortMethodModel->getValue(*artistReleaseSortMethodRow));
+
+ const bool enableInlineArtistRelationships{ Wt::asNumber(value(EnableInlineArtistRelationships)) != 0 };
+ user.modify()->setUIEnableInlineArtistRelationships(enableInlineArtistRelationships);
+
+ core::EnumSet artistLinkTypes;
+ const std::string relationships{ valueText(InlineArtistRelationships).toUTF8() };
+ for (std::string_view relationship : core::stringUtils::splitString(relationships, SelectionBox::valueSeparator))
+ {
+ auto artistRelationshipRow{ _artistRelationshipsModel->getRowFromString(Wt::WString{ std::string{ relationship } }) };
+ if (artistRelationshipRow)
+ artistLinkTypes.insert(_artistRelationshipsModel->getValue(*artistRelationshipRow));
+ }
+
+ user.modify()->setUIInlineArtistRelationships(artistLinkTypes);
}
{
@@ -248,12 +309,30 @@ namespace lms::ui
const User::pointer user{ LmsApp->getUser() };
+ // UI
{
auto artistReleaseSortMethodRow{ _artistReleaseSortMethodModel->getRowFromValue(user->getUIArtistReleaseSortMethod()) };
if (artistReleaseSortMethodRow)
setValue(ArtistReleaseSortMethodField, _artistReleaseSortMethodModel->getString(*artistReleaseSortMethodRow));
+
+ setValue(EnableInlineArtistRelationships, user->getUIEnableInlineArtistRelationships());
+ setReadOnly(InlineArtistRelationships, !user->getUIEnableInlineArtistRelationships());
+
+ Wt::WString inlineArtistRelationships;
+ for (db::TrackArtistLinkType artistLinkType : user->getUIInlineArtistRelationships())
+ {
+ if (auto artistRelationshipsRow{ _artistRelationshipsModel->getRowFromValue(artistLinkType) })
+ {
+ if (!inlineArtistRelationships.empty())
+ inlineArtistRelationships += std::string{ SelectionBox::valueSeparator };
+ inlineArtistRelationships += _artistRelationshipsModel->getString(*artistRelationshipsRow);
+ }
+ }
+
+ setValue(InlineArtistRelationships, inlineArtistRelationships);
}
+ // Audio
{
const auto settings{ *LmsApp->getMediaPlayer().getSettings() };
@@ -379,6 +458,15 @@ namespace lms::ui
_artistReleaseSortMethodModel->add(Wt::WString::tr("Lms.Settings.original-date-desc"), db::ReleaseSortMethod::OriginalDateDesc);
_artistReleaseSortMethodModel->add(Wt::WString::tr("Lms.Settings.name"), db::ReleaseSortMethod::Name);
+ _artistRelationshipsModel = std::make_shared();
+ _artistRelationshipsModel->add(Wt::WString::trn("Lms.Explore.Artists.linktype-composer", 2), db::TrackArtistLinkType::Composer);
+ _artistRelationshipsModel->add(Wt::WString::trn("Lms.Explore.Artists.linktype-conductor", 2), db::TrackArtistLinkType::Conductor);
+ _artistRelationshipsModel->add(Wt::WString::trn("Lms.Explore.Artists.linktype-lyricist", 2), db::TrackArtistLinkType::Lyricist);
+ _artistRelationshipsModel->add(Wt::WString::trn("Lms.Explore.Artists.linktype-mixer", 2), db::TrackArtistLinkType::Mixer);
+ _artistRelationshipsModel->add(Wt::WString::trn("Lms.Explore.Artists.linktype-performer", 2), db::TrackArtistLinkType::Performer);
+ _artistRelationshipsModel->add(Wt::WString::trn("Lms.Explore.Artists.linktype-producer", 2), db::TrackArtistLinkType::Producer);
+ _artistRelationshipsModel->add(Wt::WString::trn("Lms.Explore.Artists.linktype-remixer", 2), db::TrackArtistLinkType::Remixer);
+
_transcodingModeModeModel = std::make_shared();
_transcodingModeModeModel->add(Wt::WString::tr("Lms.Settings.transcoding-mode.always"), MediaPlayer::Settings::Transcoding::Mode::Always);
_transcodingModeModeModel->add(Wt::WString::tr("Lms.Settings.transcoding-mode.never"), MediaPlayer::Settings::Transcoding::Mode::Never);
@@ -422,6 +510,7 @@ namespace lms::ui
auth::IAuthTokenService& _authTokenService;
std::shared_ptr _artistReleaseSortMethodModel;
+ std::shared_ptr _artistRelationshipsModel;
std::shared_ptr _transcodingModeModeModel;
std::shared_ptr> _transcodingOutputBitrateModel;
std::shared_ptr> _transcodingOutputFormatModel;
@@ -498,6 +587,23 @@ namespace lms::ui
auto artistReleaseSortMethod{ std::make_unique() };
artistReleaseSortMethod->setModel(model->getArtistReleaseSortMethodModel());
t->setFormWidget(SettingsModel::ArtistReleaseSortMethodField, std::move(artistReleaseSortMethod));
+
+ auto enableInlineArtistRelationships{ std::make_unique() };
+ auto inlineArtistRelationships{ std::make_unique() };
+ inlineArtistRelationships->setSelectionMode(Wt::SelectionMode::Extended);
+ inlineArtistRelationships->setVerticalSize(3);
+ inlineArtistRelationships->setModel(model->getArtistRelationshipsModel());
+
+ auto updateInlineArtistRelationships{ [=](bool readOnly) {
+ model->setReadOnly(SettingsModel::InlineArtistRelationships, readOnly);
+ t->updateModel(model.get());
+ t->updateView(model.get());
+ } };
+ enableInlineArtistRelationships->checked().connect([=] { updateInlineArtistRelationships(false); });
+ enableInlineArtistRelationships->unChecked().connect([=] { updateInlineArtistRelationships(true); });
+
+ t->setFormWidget(SettingsModel::EnableInlineArtistRelationships, std::move(enableInlineArtistRelationships));
+ t->setFormWidget(SettingsModel::InlineArtistRelationships, std::move(inlineArtistRelationships));
}
// Audio
diff --git a/src/lms/ui/explore/ReleaseView.cpp b/src/lms/ui/explore/ReleaseView.cpp
index 53041fb9..11a3a86b 100644
--- a/src/lms/ui/explore/ReleaseView.cpp
+++ b/src/lms/ui/explore/ReleaseView.cpp
@@ -35,6 +35,7 @@
#include "database/Session.hpp"
#include "database/Track.hpp"
#include "database/TrackArtistLink.hpp"
+#include "database/Types.hpp"
#include "database/User.hpp"
#include "services/feedback/IFeedbackService.hpp"
#include "services/recommendation/IRecommendationService.hpp"
@@ -188,8 +189,15 @@ namespace lms::ui
void fillTrackArtistLinks(Wt::WTemplate* trackEntry, db::TrackId trackId)
{
- const std::map> artistsByRole{ TrackListHelpers::getArtistsByRole(trackId) };
+ const User::pointer user{ LmsApp->getUser() };
+ if (!user->getUIEnableInlineArtistRelationships())
+ return;
+ const core::EnumSet inlineArtistRelationships{ user->getUIInlineArtistRelationships() };
+ if (inlineArtistRelationships.empty())
+ return;
+
+ const std::map> artistsByRole{ TrackListHelpers::getArtistsByRole(trackId, inlineArtistRelationships) };
if (artistsByRole.empty())
return;
diff --git a/src/lms/ui/explore/TrackListHelpers.cpp b/src/lms/ui/explore/TrackListHelpers.cpp
index 3dada888..3093c5ac 100644
--- a/src/lms/ui/explore/TrackListHelpers.cpp
+++ b/src/lms/ui/explore/TrackListHelpers.cpp
@@ -19,8 +19,6 @@
#include "TrackListHelpers.hpp"
-#include
-
#include
#include
#include
@@ -33,6 +31,7 @@
#include "database/Track.hpp"
#include "database/TrackArtistLink.hpp"
#include "database/TrackLyrics.hpp"
+#include "database/Types.hpp"
#include "database/User.hpp"
#include "services/feedback/IFeedbackService.hpp"
#include "services/scrobbling/IScrobblingService.hpp"
@@ -50,11 +49,14 @@ namespace lms::ui::TrackListHelpers
{
using namespace db;
- std::map> getArtistsByRole(db::TrackId trackId)
+ std::map> getArtistsByRole(db::TrackId trackId, core::EnumSet artistLinkTypes)
{
std::map> artistMap;
auto addArtists = [&](TrackArtistLinkType linkType, const char* type) {
+ if (!artistLinkTypes.contains(linkType))
+ return;
+
Artist::FindParameters params;
params.setTrack(trackId);
params.setLinkType(linkType);
@@ -69,6 +71,9 @@ namespace lms::ui::TrackListHelpers
};
auto addPerformerArtists = [&] {
+ if (!artistLinkTypes.contains(db::TrackArtistLinkType::Performer))
+ return;
+
TrackArtistLink::FindParameters params;
params.setTrack(trackId);
params.setLinkType(TrackArtistLinkType::Performer);
diff --git a/src/lms/ui/explore/TrackListHelpers.hpp b/src/lms/ui/explore/TrackListHelpers.hpp
index fec10761..57f90b3c 100644
--- a/src/lms/ui/explore/TrackListHelpers.hpp
+++ b/src/lms/ui/explore/TrackListHelpers.hpp
@@ -26,9 +26,11 @@
#include
#include
+#include "core/EnumSet.hpp"
#include "database/ArtistId.hpp"
#include "database/Object.hpp"
#include "database/TrackId.hpp"
+#include "database/Types.hpp"
namespace lms::db
{
@@ -43,7 +45,17 @@ namespace lms::ui
namespace lms::ui::TrackListHelpers
{
- std::map> getArtistsByRole(db::TrackId trackId);
+ inline constexpr core::EnumSet AllArtistRoles{
+ db::TrackArtistLinkType::Composer,
+ db::TrackArtistLinkType::Conductor,
+ db::TrackArtistLinkType::Lyricist,
+ db::TrackArtistLinkType::Mixer,
+ db::TrackArtistLinkType::Remixer,
+ db::TrackArtistLinkType::Performer,
+ db::TrackArtistLinkType::Producer,
+ };
+
+ std::map> getArtistsByRole(db::TrackId trackId, core::EnumSet artistLinkTypes = AllArtistRoles);
void showTrackInfoModal(db::TrackId trackId, Filters& filters);
void showTrackLyricsModal(db::TrackId trackId);
std::unique_ptr createEntry(const db::ObjectPtr& track, PlayQueueController& playQueueController, Filters& filters);