Added an option to select how to sort albums in the artist view, fixes #498

This commit is contained in:
emeric
2024-10-04 17:39:08 +02:00
parent 0f626f7716
commit dc63e61a29
14 changed files with 188 additions and 28 deletions
+8 -1
View File
@@ -35,7 +35,7 @@ namespace lms::db
{
namespace
{
static constexpr Version LMS_DATABASE_VERSION{ 69 };
static constexpr Version LMS_DATABASE_VERSION{ 70 };
}
VersionInfo::VersionInfo()
@@ -872,6 +872,12 @@ SELECT
session.getDboSession()->execute("UPDATE scan_settings SET scan_version = scan_version + 1");
}
void migrateFromV69(Session& session)
{
// Add a field in UI settings
session.getDboSession()->execute("ALTER TABLE user ADD COLUMN ui_artist_release_sort_method NOT NULL DEFAULT 7"); // 7 = ReleaseSortMethod::OriginalDateDesc
}
bool doDbMigration(Session& session)
{
constexpr std::string_view outdatedMsg{ "Outdated database, please rebuild it (delete the .db file and restart)" };
@@ -917,6 +923,7 @@ SELECT
{ 66, migrateFromV66 },
{ 67, migrateFromV67 },
{ 68, migrateFromV68 },
{ 69, migrateFromV69 },
};
bool migrationPerformed{};
+5 -1
View File
@@ -75,6 +75,7 @@ namespace lms::db
static inline constexpr TranscodingOutputFormat defaultSubsonicTranscodingOutputFormat{ TranscodingOutputFormat::OGG_OPUS };
static inline constexpr Bitrate defaultSubsonicTranscodingOutputBitrate{ 128000 };
static inline constexpr UITheme defaultUITheme{ UITheme::Dark };
static inline constexpr ReleaseSortMethod _defaultUIArtistReleaseSortMethod{ ReleaseSortMethod::OriginalDateDesc };
static inline constexpr SubsonicArtistListMode defaultSubsonicArtistListMode{ SubsonicArtistListMode::AllArtists };
static inline constexpr ScrobblingBackend defaultScrobblingBackend{ ScrobblingBackend::Internal };
static inline constexpr FeedbackBackend defaultFeedbackBackend{ FeedbackBackend::Internal };
@@ -106,6 +107,7 @@ namespace lms::db
void setSubsonicDefaultTranscodintOutputFormat(TranscodingOutputFormat encoding) { _subsonicDefaultTranscodingOutputFormat = encoding; }
void setSubsonicDefaultTranscodingOutputBitrate(Bitrate bitrate);
void setUITheme(UITheme uiTheme) { _uiTheme = uiTheme; }
void setUIArtistReleaseSortMethod(ReleaseSortMethod method) { _uiArtistReleaseSortMethod = method; }
void clearAuthTokens();
void setSubsonicArtistListMode(SubsonicArtistListMode mode) { _subsonicArtistListMode = mode; }
void setFeedbackBackend(FeedbackBackend feedbackBackend) { _feedbackBackend = feedbackBackend; }
@@ -120,6 +122,7 @@ namespace lms::db
TranscodingOutputFormat getSubsonicDefaultTranscodingOutputFormat() const { return _subsonicDefaultTranscodingOutputFormat; }
Bitrate getSubsonicDefaultTranscodingOutputBitrate() const { return _subsonicDefaultTranscodingOutputBitrate; }
UITheme getUITheme() const { return _uiTheme; }
ReleaseSortMethod getUIArtistReleaseSortMethod() const { return _uiArtistReleaseSortMethod; }
SubsonicArtistListMode getSubsonicArtistListMode() const { return _subsonicArtistListMode; }
FeedbackBackend getFeedbackBackend() const { return _feedbackBackend; }
ScrobblingBackend getScrobblingBackend() const { return _scrobblingBackend; }
@@ -138,6 +141,7 @@ namespace lms::db
Wt::Dbo::field(a, _subsonicDefaultTranscodingOutputBitrate, "subsonic_default_transcode_bitrate");
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, _feedbackBackend, "feedback_backend");
Wt::Dbo::field(a, _scrobblingBackend, "scrobbling_backend");
Wt::Dbo::field(a, _listenbrainzToken, "listenbrainz_token");
@@ -156,6 +160,7 @@ namespace lms::db
std::string _passwordHash;
Wt::WDateTime _lastLogin;
UITheme _uiTheme{ defaultUITheme };
ReleaseSortMethod _uiArtistReleaseSortMethod{ _defaultUIArtistReleaseSortMethod };
FeedbackBackend _feedbackBackend{ defaultFeedbackBackend };
ScrobblingBackend _scrobblingBackend{ defaultScrobblingBackend };
std::string _listenbrainzToken; // Musicbrainz Identifier
@@ -172,5 +177,4 @@ namespace lms::db
Wt::Dbo::collection<Wt::Dbo::ptr<AuthToken>> _authTokens;
Wt::Dbo::collection<Wt::Dbo::ptr<UIState>> _uiStates;
};
} // namespace lms::db
+1
View File
@@ -12,6 +12,7 @@ add_executable(lms
ui/PlayQueue.cpp
ui/SettingsView.cpp
ui/State.cpp
ui/Tooltip.cpp
ui/Utils.cpp
ui/admin/InitWizardView.cpp
ui/admin/MediaLibrariesView.cpp
+50 -18
View File
@@ -37,6 +37,7 @@
#include "LmsApplication.hpp"
#include "MediaPlayer.hpp"
#include "Tooltip.hpp"
#include "common/DoubleValidator.hpp"
#include "common/MandatoryValidator.hpp"
#include "common/PasswordValidator.hpp"
@@ -51,6 +52,7 @@ 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 TranscodingModeField{ "transcoding-mode" };
static inline const Field TranscodeFormatField{ "transcoding-output-format" };
static inline const Field TranscodeBitrateField{ "transcoding-output-bitrate" };
@@ -68,10 +70,11 @@ namespace lms::ui
static inline const Field PasswordField{ "password" };
static inline const Field PasswordConfirmField{ "password-confirm" };
using ArtistReleaseSortMethodModel = ValueStringModel<db::ReleaseSortMethod>;
using TranscodingModeModel = ValueStringModel<MediaPlayer::Settings::Transcoding::Mode>;
using ReplayGainModeModel = ValueStringModel<MediaPlayer::Settings::ReplayGain::Mode>;
using FeedbackBackendModel = ValueStringModel<FeedbackBackend>;
using ScrobblingBackendModel = ValueStringModel<ScrobblingBackend>;
using FeedbackBackendModel = ValueStringModel<db::FeedbackBackend>;
using ScrobblingBackendModel = ValueStringModel<db::ScrobblingBackend>;
SettingsModel(auth::IPasswordService* authPasswordService, bool withOldPassword)
: _authPasswordService{ authPasswordService }
@@ -79,6 +82,7 @@ namespace lms::ui
{
initializeModels();
addField(ArtistReleaseSortMethodField);
addField(TranscodingModeField);
addField(TranscodeBitrateField);
addField(TranscodeFormatField);
@@ -106,6 +110,7 @@ namespace lms::ui
addField(PasswordConfirmField);
}
setValidator(ArtistReleaseSortMethodField, createMandatoryValidator());
setValidator(TranscodingModeField, createMandatoryValidator());
setValidator(TranscodeBitrateField, createMandatoryValidator());
setValidator(TranscodeFormatField, createMandatoryValidator());
@@ -122,6 +127,7 @@ namespace lms::ui
loadData();
}
std::shared_ptr<ArtistReleaseSortMethodModel> getArtistReleaseSortMethodModel() { return _artistReleaseSortMethodModel; }
std::shared_ptr<TranscodingModeModel> getTranscodingModeModel() { return _transcodingModeModeModel; }
std::shared_ptr<Wt::WAbstractItemModel> getTranscodingOutputBitrateModel() { return _transcodingOutputBitrateModel; }
std::shared_ptr<Wt::WAbstractItemModel> getTranscodingOutputFormatModel() { return _transcodingOutputFormatModel; }
@@ -136,6 +142,12 @@ namespace lms::ui
User::pointer user{ LmsApp->getUser() };
{
auto artistReleaseSortMethodRow{ _artistReleaseSortMethodModel->getRowFromString(valueText(ArtistReleaseSortMethodField)) };
if (artistReleaseSortMethodRow)
user.modify()->setUIArtistReleaseSortMethod(_artistReleaseSortMethodModel->getValue(*artistReleaseSortMethodRow));
}
{
MediaPlayer::Settings settings;
@@ -204,6 +216,12 @@ namespace lms::ui
User::pointer user{ LmsApp->getUser() };
{
auto artistReleaseSortMethodRow{ _artistReleaseSortMethodModel->getRowFromValue(user->getUIArtistReleaseSortMethod()) };
if (artistReleaseSortMethodRow)
setValue(ArtistReleaseSortMethodField, _artistReleaseSortMethodModel->getString(*artistReleaseSortMethodRow));
}
{
const auto settings{ *LmsApp->getMediaPlayer().getSettings() };
@@ -315,6 +333,13 @@ namespace lms::ui
private:
void initializeModels()
{
_artistReleaseSortMethodModel = std::make_shared<ArtistReleaseSortMethodModel>();
_artistReleaseSortMethodModel->add(Wt::WString::tr("Lms.Settings.date-asc"), db::ReleaseSortMethod::DateAsc);
_artistReleaseSortMethodModel->add(Wt::WString::tr("Lms.Settings.date-desc"), db::ReleaseSortMethod::DateDesc);
_artistReleaseSortMethodModel->add(Wt::WString::tr("Lms.Settings.original-date-asc"), db::ReleaseSortMethod::OriginalDate);
_artistReleaseSortMethodModel->add(Wt::WString::tr("Lms.Settings.original-date-desc"), db::ReleaseSortMethod::OriginalDateDesc);
_artistReleaseSortMethodModel->add(Wt::WString::tr("Lms.Settings.name"), db::ReleaseSortMethod::Name);
_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);
@@ -355,6 +380,7 @@ namespace lms::ui
auth::IPasswordService* _authPasswordService{};
bool _withOldPassword{};
std::shared_ptr<ArtistReleaseSortMethodModel> _artistReleaseSortMethodModel;
std::shared_ptr<TranscodingModeModel> _transcodingModeModeModel;
std::shared_ptr<ValueStringModel<Bitrate>> _transcodingOutputBitrateModel;
std::shared_ptr<ValueStringModel<TranscodingOutputFormat>> _transcodingOutputFormatModel;
@@ -425,12 +451,25 @@ namespace lms::ui
t->setFormWidget(SettingsModel::PasswordConfirmField, std::move(passwordConfirm));
}
// User interface
{
auto artistReleaseSortMethod{ std::make_unique<Wt::WComboBox>() };
artistReleaseSortMethod->setModel(model->getArtistReleaseSortMethodModel());
t->setFormWidget(SettingsModel::ArtistReleaseSortMethodField, std::move(artistReleaseSortMethod));
}
// Audio
{
// Transcode
auto transcodingMode{ std::make_unique<Wt::WComboBox>() };
auto* transcodingModeRaw{ transcodingMode.get() };
transcodingMode->setModel(model->getTranscodingModeModel());
transcodingMode->activated().connect([=](int row) {
const bool enable{ model->getTranscodingModeModel()->getValue(row) != MediaPlayer::Settings::Transcoding::Mode::Never };
model->setReadOnly(SettingsModel::TranscodeFormatField, !enable);
model->setReadOnly(SettingsModel::TranscodeBitrateField, !enable);
t->updateModel(model.get());
t->updateView(model.get());
});
t->setFormWidget(SettingsModel::TranscodingModeField, std::move(transcodingMode));
// Format
@@ -443,17 +482,15 @@ namespace lms::ui
transcodingOutputBitrate->setModel(model->getTranscodingOutputBitrateModel());
t->setFormWidget(SettingsModel::TranscodeBitrateField, std::move(transcodingOutputBitrate));
transcodingModeRaw->activated().connect([=](int row) {
const bool enable{ model->getTranscodingModeModel()->getValue(row) != MediaPlayer::Settings::Transcoding::Mode::Never };
model->setReadOnly(SettingsModel::TranscodeFormatField, !enable);
model->setReadOnly(SettingsModel::TranscodeBitrateField, !enable);
// Replay gain mode
auto replayGainMode{ std::make_unique<Wt::WComboBox>() };
replayGainMode->activated().connect([=](int row) {
const bool enable{ model->getReplayGainModeModel()->getValue(row) != MediaPlayer::Settings::ReplayGain::Mode::None };
model->setReadOnly(SettingsModel::SettingsModel::ReplayGainPreAmpGainField, !enable);
model->setReadOnly(SettingsModel::SettingsModel::ReplayGainPreAmpGainIfNoInfoField, !enable);
t->updateModel(model.get());
t->updateView(model.get());
});
// Replay gain mode
auto replayGainMode{ std::make_unique<Wt::WComboBox>() };
auto* replayGainModeRaw{ replayGainMode.get() };
replayGainMode->setModel(model->getReplayGainModeModel());
t->setFormWidget(SettingsModel::ReplayGainModeField, std::move(replayGainMode));
@@ -467,13 +504,6 @@ namespace lms::ui
replayGainPreampGainIfNoInfo->setRange(MediaPlayer::Settings::ReplayGain::minPreAmpGain, MediaPlayer::Settings::ReplayGain::maxPreAmpGain);
t->setFormWidget(SettingsModel::ReplayGainPreAmpGainIfNoInfoField, std::move(replayGainPreampGainIfNoInfo));
replayGainModeRaw->activated().connect([=](int row) {
const bool enable{ model->getReplayGainModeModel()->getValue(row) != MediaPlayer::Settings::ReplayGain::Mode::None };
model->setReadOnly(SettingsModel::SettingsModel::ReplayGainPreAmpGainField, !enable);
model->setReadOnly(SettingsModel::SettingsModel::ReplayGainPreAmpGainIfNoInfoField, !enable);
t->updateModel(model.get());
t->updateView(model.get());
});
if (LmsApp->getMediaPlayer().getSettings()->replayGain.mode == MediaPlayer::Settings::ReplayGain::Mode::None)
{
model->setReadOnly(SettingsModel::SettingsModel::ReplayGainPreAmpGainField, true);
@@ -573,6 +603,8 @@ namespace lms::ui
});
t->updateView(model.get());
initTooltipsForWidgetTree(*t);
}
} // namespace lms::ui
+39
View File
@@ -0,0 +1,39 @@
/*
* Copyright (C) 2024 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 "Tooltip.hpp"
#include "core/ILogger.hpp"
#include <Wt/WWebWidget.h>
namespace lms::ui
{
void initTooltipsForWidgetTree(Wt::WWebWidget& widget)
{
std::ostringstream oss;
oss << R"({const rootElement = document.getElementById(')" << widget.id() << R"(');)"
<< R"(const tooltipTriggerList = rootElement.querySelectorAll('[data-bs-toggle="tooltip"]');
tooltipTriggerList.forEach(tooltipTriggerEl => {
new bootstrap.Tooltip(tooltipTriggerEl);
});})";
LMS_LOG(UI, DEBUG, "Running JS '" << oss.str() << "'");
widget.doJavaScript(oss.str());
}
} // namespace lms::ui
+30
View File
@@ -0,0 +1,30 @@
/*
* Copyright (C) 2024 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
namespace Wt
{
class WWebWidget;
}
namespace lms::ui
{
void initTooltipsForWidgetTree(Wt::WWebWidget& widget);
}
+2 -2
View File
@@ -40,7 +40,7 @@ namespace lms::ui
return Wt::cpp17::any_cast<Wt::WString>(data(index(static_cast<int>(row), 0), Wt::ItemDataRole::Display));
}
std::optional<std::size_t> getRowFromString(const Wt::WString& value)
std::optional<std::size_t> getRowFromString(const Wt::WString& value) const
{
for (std::size_t i{}; i < static_cast<std::size_t>(rowCount()); ++i)
{
@@ -51,7 +51,7 @@ namespace lms::ui
return std::nullopt;
}
std::optional<std::size_t> getRowFromValue(const T& value)
std::optional<std::size_t> getRowFromValue(const T& value) const
{
for (std::size_t i{}; i < static_cast<std::size_t>(rowCount()); ++i)
{
+1 -1
View File
@@ -197,7 +197,7 @@ namespace lms::ui
params.setClusters(_filters.getClusters());
params.setMediaLibrary(_filters.getMediaLibrary());
params.setArtist(_artistId, { TrackArtistLinkType::ReleaseArtist }, {});
params.setSortMethod(ReleaseSortMethod::OriginalDateDesc);
params.setSortMethod(LmsApp->getUser()->getUIArtistReleaseSortMethod());
const auto releases{ Release::findIds(LmsApp->getDbSession(), params) };
if (!releases.results.empty())