diff --git a/approot/explore.xml b/approot/explore.xml index d36f0def..36eef544 100644 --- a/approot/explore.xml +++ b/approot/explore.xml @@ -32,9 +32,9 @@ ${value class="form-control"} - + + diff --git a/approot/messages.xml b/approot/messages.xml index 9d3ad6f4..bb07bcc6 100644 --- a/approot/messages.xml +++ b/approot/messages.xml @@ -26,6 +26,10 @@ Passwords don't match Another session has been open. Reopen this one? Save + + {1} track + {1} tracks + User @@ -157,12 +161,6 @@ Copyright Disc {1} - - - {1} track - {1} tracks - - Delete playlist? @@ -174,7 +172,7 @@ Clear -{1} tracks +Create new playlist Added {1} track Added {1} tracks @@ -183,6 +181,7 @@ Play Queue full! Radio mode Repeat +Replace existing playlist Shuffle diff --git a/approot/messages_fr.xml b/approot/messages_fr.xml index 9da6e08a..8d6a1b38 100644 --- a/approot/messages_fr.xml +++ b/approot/messages_fr.xml @@ -26,6 +26,10 @@ Les mots de passe ne correspondent pas Une autre session a été ouverte. Reouvrir celle-ci ? Sauvegarder + + {1} piste + {1} pistes + Utilisateur @@ -157,12 +161,6 @@ Copyright Disque {1} - - - {1} piste - {1} pistes - - Supprimer la liste de lecture ? @@ -174,7 +172,7 @@ Effacer -{1} pistes +Créer une nouvelle liste de lecture {1} piste ajoutée {1} pistes ajoutées @@ -183,12 +181,12 @@ Liste de lecture pleine! Mode radio Repéter +Remplacer une liste de lecture existante Mélanger Historique de lecture - Audio Ces paramètres audio sont locaux à votre navigateur ! diff --git a/approot/messages_it.xml b/approot/messages_it.xml index e779790f..4a254842 100644 --- a/approot/messages_it.xml +++ b/approot/messages_it.xml @@ -26,6 +26,10 @@ Le password non corrispondono Un'altra sessione è stata aperta. Vuoi riaprire questa? Salva + + + + Utente @@ -46,6 +50,7 @@ Raccolta musicale Mai Cartella dei file multimediali + Modalità di raccomandazione brani consigliati Basata sui tag Basata sull'analisi acustica @@ -156,12 +161,6 @@ Copyright Disco {1} - - - - - - @@ -173,7 +172,7 @@ Cancella -{1} tracce + Aggiunta {1} traccia Aggiunte {1} tracce @@ -182,12 +181,12 @@ Coda di riproduzione piena! Modalità radio Ripeti + Mischia Storico riproduzione - Audio Queste impostazioni sono salvate localmente su questo dispositivo! @@ -203,6 +202,11 @@ Album Preamplificazione ReplayGain Preamplificazione ReplayGain (se non è disponibile nessuna informazione) + + + + + Modalità di elencazione artisti Tutti gli artisti Artisti album diff --git a/approot/messages_zh.xml b/approot/messages_zh.xml index 40f02769..86598f6c 100644 --- a/approot/messages_zh.xml +++ b/approot/messages_zh.xml @@ -26,6 +26,10 @@ 密码不匹配 另一个会话已打开,重新打开这一个? 保存 + + + + 用户 @@ -157,12 +161,6 @@ 版权所有 唱片 {1} - - - - - - @@ -174,7 +172,7 @@ 清除 -{1} 音轨 + 添加 {1} 音轨 已添加 {1} 音轨 @@ -183,6 +181,7 @@ 播放队列已满! 广播模式 循环播放 + 随机播放 @@ -203,6 +202,11 @@ 专辑 ReplayGain 放大器 ReplayGain 放大器 (如果没有信息) + + + + + 歌手列表模式 所有歌手 专辑歌手 diff --git a/approot/playqueue.xml b/approot/playqueue.xml index 21a57b44..c45dcf7a 100644 --- a/approot/playqueue.xml +++ b/approot/playqueue.xml @@ -6,22 +6,26 @@ +
-
- ${clear-btn class="btn btn-secondary"} -
-
- ${shuffle-btn class="btn btn-secondary"} -
${repeat-btn class="btn-check"} ${radio-btn class="btn-check"}
- ${nb-tracks} +
+ ${save-btn class="btn btn-secondary"} +
+
+ ${shuffle-btn class="btn btn-secondary"} +
+
+ ${clear-btn class="btn btn-warning"} +
+ ${track-count}
${entries}
@@ -56,4 +60,49 @@ ${artist}
+ + + + + + ${name class="form-control"} +
+ ${name-info} +
+
+ + + ${name class="form-control"} +
+ ${name-info} +
+
+ diff --git a/src/libs/services/database/impl/Track.cpp b/src/libs/services/database/impl/Track.cpp index b6f22f7d..269ba3a9 100644 --- a/src/libs/services/database/impl/Track.cpp +++ b/src/libs/services/database/impl/Track.cpp @@ -43,7 +43,7 @@ createQuery(Session& session, const Track::FindParameters& params) { session.checkSharedLocked(); - auto query {session.getDboSession().query("SELECT DISTINCT t.id from track t")}; + auto query {session.getDboSession().query(params.distinct ? "SELECT DISTINCT t.id FROM track t" : "SELECT t.id FROM track t")}; for (std::string_view keyword : params.keywords) query.where("t.name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'").bind("%" + Utils::escapeLikeKeyword(keyword) + "%"); diff --git a/src/libs/services/database/include/services/database/Track.hpp b/src/libs/services/database/include/services/database/Track.hpp index f47d903b..9f5430d7 100644 --- a/src/libs/services/database/include/services/database/Track.hpp +++ b/src/libs/services/database/include/services/database/Track.hpp @@ -71,6 +71,7 @@ class Track : public Object bool nonRelease {}; // only tracks that do not belong to a release ReleaseId release; // matching this release TrackListId trackList; // matching this trackList + bool distinct {true}; FindParameters& setClusters(const std::vector& _clusters) { clusters = _clusters; return *this; } FindParameters& setKeywords(const std::vector& _keywords) { keywords = _keywords; return *this; } @@ -82,6 +83,7 @@ class Track : public Object FindParameters& setNonRelease(bool _nonRelease) { nonRelease = _nonRelease; return *this; } FindParameters& setRelease(ReleaseId _release) { release = _release; return *this; } FindParameters& setTrackList(TrackListId _trackList) { trackList = _trackList; return *this; } + FindParameters& setDistinct(bool _distinct) { distinct = _distinct; return *this; } }; struct PathResult diff --git a/src/lms/ui/PlayQueue.cpp b/src/lms/ui/PlayQueue.cpp index 58a481ff..939b8e54 100644 --- a/src/lms/ui/PlayQueue.cpp +++ b/src/lms/ui/PlayQueue.cpp @@ -20,7 +20,13 @@ #include "PlayQueue.hpp" #include +#include +#include +#include +#include #include +#include +#include #include "services/database/Cluster.hpp" #include "services/database/Release.hpp" @@ -36,13 +42,79 @@ #include "utils/String.hpp" #include "common/InfiniteScrollingContainer.hpp" +#include "common/MandatoryValidator.hpp" +#include "common/ValueStringModel.hpp" #include "resource/DownloadResource.hpp" #include "LmsApplication.hpp" #include "MediaPlayer.hpp" +#include "ModalManager.hpp" #include "Utils.hpp" namespace UserInterface { +namespace +{ + class CreateTrackListModel : public Wt::WFormModel + { + public: + static inline const Field NameField {"name"}; + + CreateTrackListModel() + { + addField(NameField); + setValidator(NameField, createMandatoryValidator()); + } + + Wt::WString getName() const { return valueText(NameField); } + }; + + class ReplaceTrackListModel : public Wt::WFormModel + { + public: + static inline const Field NameField {"name"}; + using TrackListModel = ValueStringModel; + + ReplaceTrackListModel() + { + addField(NameField); + setValidator(NameField, createMandatoryValidator()); + } + + Database::TrackListId getTrackListId() const + { + auto row {trackListModel->getRowFromString(valueText(NameField))}; + return trackListModel->getValue(*row); + } + + static + std::shared_ptr + createTrackListModel() + { + using namespace Database; + + auto model {std::make_shared()}; + + auto transaction {LmsApp->getDbSession().createSharedTransaction()}; + + TrackList::FindParameters params; + params.setType(TrackListType::Playlist); + params.setUser(LmsApp->getUserId()); + params.setSortMethod(TrackListSortMethod::Name); + + auto tracklists {TrackList::find(LmsApp->getDbSession(), params)}; + for (const TrackListId trackListId : tracklists.results) + { + const TrackList::pointer trackList {TrackList::find(LmsApp->getDbSession(), trackListId)}; + model->add(Wt::WString::fromUTF8(std::string {trackList->getName()}), trackListId); + } + + return model; + } + + std::shared_ptr trackListModel {createTrackListModel()}; + }; +} + PlayQueue::PlayQueue() : Template {Wt::WString::tr("Lms.PlayQueue.template")} { @@ -55,6 +127,12 @@ PlayQueue::PlayQueue() clearTracks(); }); + Wt::WPushButton* saveBtn {bindNew("save-btn", Wt::WString::tr("Lms.PlayQueue.template.save-btn"), Wt::TextFormat::XHTML)}; + saveBtn->clicked().connect([=] + { + saveAsTrackList(); + }); + _entriesContainer = bindNew("entries"); _entriesContainer->onRequestElements.connect([this] { @@ -108,7 +186,7 @@ PlayQueue::PlayQueue() _radioBtn->setCheckState(Wt::CheckState::Checked); } - _nbTracks = bindNew("nb-tracks"); + _nbTracks = bindNew("track-count"); LmsApp->preQuit().connect([=] { @@ -287,7 +365,8 @@ PlayQueue::updateInfo() { auto transaction {LmsApp->getDbSession().createSharedTransaction()}; - _nbTracks->setText(Wt::WString::tr("Lms.PlayQueue.nb-tracks").arg(static_cast(getTrackList()->getCount()))); + const auto trackCount {getTrackList()->getCount()}; + _nbTracks->setText(Wt::WString::trn("Lms.track-count", trackCount).arg(trackCount)); } void @@ -541,5 +620,133 @@ PlayQueue::getReplayGain(std::size_t pos, const Database::Track::pointer& track) return settings->replayGain.preAmpGainIfNoInfo; } +void +PlayQueue::saveAsTrackList() +{ + auto modal {std::make_unique