Added settings to contro which kind of artist relationship to display inline

This commit is contained in:
emeric
2025-03-08 22:55:38 +01:00
parent bedf972769
commit d52addb9db
13 changed files with 188 additions and 9 deletions
+2
View File
@@ -309,8 +309,10 @@
<message id="Lms.Settings.default-transcoding-output-bitrate">Default transcoding output bitrate</message>
<message id="Lms.Settings.default-transcoding-output-format">Default transcoding output format</message>
<message id="Lms.Settings.demo-cannot-save">Cannot save using a demo account!</message>
<message id="Lms.Settings.enable-inline-artist-relationships">Display artist relationships inline within albums</message>
<message id="Lms.Settings.enable-transcoding-by-default">Enable transcoding by default</message>
<message id="Lms.Settings.feedback">Feedback</message>
<message id="Lms.Settings.inline-artist-relationships">Types of artist relationships to display</message>
<message id="Lms.Settings.menu-settings"><i class="fa fa-fw fa-cog" aria-hidden="true"></i> Settings</message>
<message id="Lms.Settings.name">Album Name</message>
<message id="Lms.Settings.original-date-asc">Original Release Date (Ascending)</message>
+2
View File
@@ -309,8 +309,10 @@
<message id="Lms.Settings.default-transcoding-output-bitrate">Bitrate par défaut du transcodage</message>
<message id="Lms.Settings.default-transcoding-output-format">Format par défaut du transcodage</message>
<message id="Lms.Settings.demo-cannot-save">Impossible de sauvegarder en utilisant un compte de démo !</message>
<message id="Lms.Settings.enable-inline-artist-relationships">Afficher les relations d'artiste en ligne dans les albums</message>
<message id="Lms.Settings.enable-transcoding-by-default">Activer par défaut le transcodage</message>
<message id="Lms.Settings.feedback">Feedback</message>
<message id="Lms.Settings.inline-artist-relationships">Types de relations d'artiste à afficher</message>
<message id="Lms.Settings.menu-settings"><i class="fa fa-fw fa-cog" aria-hidden="true"></i> Paramètres</message>
<message id="Lms.Settings.name">Nom de l'album</message>
<message id="Lms.Settings.original-date-asc">Date de sortie originale (Croissant)</message>
+2
View File
@@ -309,8 +309,10 @@
<message id="Lms.Settings.default-transcoding-output-bitrate">Bitrate di default per la transcodifica</message>
<message id="Lms.Settings.default-transcoding-output-format">Formato di default per la transcodifica</message>
<message id="Lms.Settings.demo-cannot-save">Non posso salvare le impostazioni utilizzando l'account demo!</message>
<message id="Lms.Settings.enable-inline-artist-relationships">Mostra le relazioni degli artisti in linea negli album</message>
<message id="Lms.Settings.enable-transcoding-by-default">Abilita transcodifica di default</message>
<message id="Lms.Settings.feedback">Feedback</message>
<message id="Lms.Settings.inline-artist-relationships">Tipi di relazioni artistiche da visualizzare</message>
<message id="Lms.Settings.menu-settings"><i class="fa fa-fw fa-cog" aria-hidden="true"></i> Impostazioni</message>
<message id="Lms.Settings.name">Nome dell'album</message>
<message id="Lms.Settings.original-date-asc">Data di uscita originale (Crescente)</message>
+2
View File
@@ -335,8 +335,10 @@
<message id="Lms.Settings.default-transcoding-output-bitrate">Domyślna przepływność transkodowania</message>
<message id="Lms.Settings.default-transcoding-output-format">Domyślny format wyjściowy transkodowania</message>
<message id="Lms.Settings.demo-cannot-save">Nie można zapisywać na koncie demonstracyjnym!</message>
<message id="Lms.Settings.enable-inline-artist-relationships">Wyświetlaj powiązania artystów w albumach</message>
<message id="Lms.Settings.enable-transcoding-by-default">Aktywuj domyślnie transkodowanie</message>
<message id="Lms.Settings.feedback">Feedback</message>
<message id="Lms.Settings.inline-artist-relationships">Typy powiązań artystów do wyświetlenia</message>
<message id="Lms.Settings.menu-settings"><i class="fa fa-fw fa-cog" aria-hidden="true"></i> Ustawienia</message>
<message id="Lms.Settings.name">Nazwa albumu</message>
<message id="Lms.Settings.original-date-asc">Oryginalna data wydania (Rosnąco)</message>
+2
View File
@@ -311,6 +311,8 @@
<message id="Lms.Settings.demo-cannot-save">使用演示账号时不能保存!</message>
<message id="Lms.Settings.menu-settings"><i class="fa fa-fw fa-cog" aria-hidden="true"></i> 设置</message>
+20
View File
@@ -15,6 +15,26 @@
${artist-release-sort-method-info}
</div>
</div>
<div class="col-12">
<div class="form-check">
${enable-inline-artist-relationships class="form-check-input"}
<label class="form-check-label" for="${id:enable-inline-artist-relationships}">
${tr:Lms.Settings.enable-inline-artist-relationships}
</label>
<div class="invalid-feedback">
${enable-inline-artist-relationships}
</div>
</div>
</div>
<div class="col-12">
<label class="form-label" for="${id:inline-artist-relationships}">
${tr:Lms.Settings.inline-artist-relationships}
</label>
${inline-artist-relationships class="form-control"}
<div class="invalid-feedback">
${inline-artist-relationships-info}
</div>
</div>
<legend>${tr:Lms.Settings.audio}
<button type="button" class="btn btn-sm p-0" data-bs-toggle="tooltip" data-bs-placement="right" title="${tr:Lms.Settings.audio-settings-are-local}">
<i class="fa fa-fw fa-info-circle" aria-hidden="true"></i>
+9 -1
View File
@@ -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{};
+3 -1
View File
@@ -19,13 +19,15 @@
#include "database/User.hpp"
#include "core/ILogger.hpp"
#include <cassert>
#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"
+9 -1
View File
@@ -21,11 +21,11 @@
#include <optional>
#include <string_view>
#include <vector>
#include <Wt/Dbo/Dbo.h>
#include <Wt/WDateTime.h>
#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<TrackArtistLinkType> 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<TrackArtistLinkType> 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<TrackArtistLinkType> _uiInlineArtistRelationships{ TrackArtistLinkType::Composer, TrackArtistLinkType::Performer };
FeedbackBackend _feedbackBackend{ defaultFeedbackBackend };
ScrobblingBackend _scrobblingBackend{ defaultScrobblingBackend };
std::string _listenbrainzToken; // Musicbrainz Identifier
+107 -1
View File
@@ -25,12 +25,15 @@
#include <Wt/WFormModel.h>
#include <Wt/WLineEdit.h>
#include <Wt/WPushButton.h>
#include <Wt/WSelectionBox.h>
#include <Wt/WString.h>
#include <Wt/WTemplateFormView.h>
#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<int> 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<db::ReleaseSortMethod>;
using ArtistRelationshipsModel = ValueStringModel<db::TrackArtistLinkType>;
using TranscodingModeModel = ValueStringModel<MediaPlayer::Settings::Transcoding::Mode>;
using ReplayGainModeModel = ValueStringModel<MediaPlayer::Settings::ReplayGain::Mode>;
using FeedbackBackendModel = ValueStringModel<db::FeedbackBackend>;
@@ -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<ArtistReleaseSortMethodModel> getArtistReleaseSortMethodModel() { return _artistReleaseSortMethodModel; }
std::shared_ptr<ArtistRelationshipsModel> getArtistRelationshipsModel() { return _artistRelationshipsModel; }
std::shared_ptr<TranscodingModeModel> getTranscodingModeModel() { return _transcodingModeModeModel; }
std::shared_ptr<Wt::WAbstractItemModel> getTranscodingOutputBitrateModel() { return _transcodingOutputBitrateModel; }
std::shared_ptr<Wt::WAbstractItemModel> 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<db::TrackArtistLinkType> 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>();
_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<TranscodingModeModel>();
_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> _artistReleaseSortMethodModel;
std::shared_ptr<ArtistRelationshipsModel> _artistRelationshipsModel;
std::shared_ptr<TranscodingModeModel> _transcodingModeModeModel;
std::shared_ptr<ValueStringModel<Bitrate>> _transcodingOutputBitrateModel;
std::shared_ptr<ValueStringModel<TranscodingOutputFormat>> _transcodingOutputFormatModel;
@@ -498,6 +587,23 @@ namespace lms::ui
auto artistReleaseSortMethod{ std::make_unique<Wt::WComboBox>() };
artistReleaseSortMethod->setModel(model->getArtistReleaseSortMethodModel());
t->setFormWidget(SettingsModel::ArtistReleaseSortMethodField, std::move(artistReleaseSortMethod));
auto enableInlineArtistRelationships{ std::make_unique<Wt::WCheckBox>() };
auto inlineArtistRelationships{ std::make_unique<SelectionBox>() };
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
+9 -1
View File
@@ -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<Wt::WString, std::set<ArtistId>> artistsByRole{ TrackListHelpers::getArtistsByRole(trackId) };
const User::pointer user{ LmsApp->getUser() };
if (!user->getUIEnableInlineArtistRelationships())
return;
const core::EnumSet<db::TrackArtistLinkType> inlineArtistRelationships{ user->getUIInlineArtistRelationships() };
if (inlineArtistRelationships.empty())
return;
const std::map<Wt::WString, std::set<ArtistId>> artistsByRole{ TrackListHelpers::getArtistsByRole(trackId, inlineArtistRelationships) };
if (artistsByRole.empty())
return;
+8 -3
View File
@@ -19,8 +19,6 @@
#include "TrackListHelpers.hpp"
#include <map>
#include <Wt/WAnchor.h>
#include <Wt/WImage.h>
#include <Wt/WPushButton.h>
@@ -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<Wt::WString, std::set<ArtistId>> getArtistsByRole(db::TrackId trackId)
std::map<Wt::WString, std::set<ArtistId>> getArtistsByRole(db::TrackId trackId, core::EnumSet<db::TrackArtistLinkType> artistLinkTypes)
{
std::map<Wt::WString, std::set<ArtistId>> 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);
+13 -1
View File
@@ -26,9 +26,11 @@
#include <Wt/WString.h>
#include <Wt/WWidget.h>
#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<Wt::WString, std::set<db::ArtistId>> getArtistsByRole(db::TrackId trackId);
inline constexpr core::EnumSet<db::TrackArtistLinkType> AllArtistRoles{
db::TrackArtistLinkType::Composer,
db::TrackArtistLinkType::Conductor,
db::TrackArtistLinkType::Lyricist,
db::TrackArtistLinkType::Mixer,
db::TrackArtistLinkType::Remixer,
db::TrackArtistLinkType::Performer,
db::TrackArtistLinkType::Producer,
};
std::map<Wt::WString, std::set<db::ArtistId>> getArtistsByRole(db::TrackId trackId, core::EnumSet<db::TrackArtistLinkType> artistLinkTypes = AllArtistRoles);
void showTrackInfoModal(db::TrackId trackId, Filters& filters);
void showTrackLyricsModal(db::TrackId trackId);
std::unique_ptr<Wt::WWidget> createEntry(const db::ObjectPtr<db::Track>& track, PlayQueueController& playQueueController, Filters& filters);