Added a way to save the current playqueue in a playlist

This commit is contained in:
emeric
2022-07-02 15:39:50 +02:00
parent 27cad7ba0e
commit 1c7ef90108
12 changed files with 314 additions and 62 deletions
+1 -1
View File
@@ -43,7 +43,7 @@ createQuery(Session& session, const Track::FindParameters& params)
{
session.checkSharedLocked();
auto query {session.getDboSession().query<TrackId>("SELECT DISTINCT t.id from track t")};
auto query {session.getDboSession().query<TrackId>(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) + "%");
@@ -71,6 +71,7 @@ class Track : public Object<Track, TrackId>
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<ClusterId>& _clusters) { clusters = _clusters; return *this; }
FindParameters& setKeywords(const std::vector<std::string_view>& _keywords) { keywords = _keywords; return *this; }
@@ -82,6 +83,7 @@ class Track : public Object<Track, TrackId>
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
+209 -2
View File
@@ -20,7 +20,13 @@
#include "PlayQueue.hpp"
#include <Wt/WCheckBox.h>
#include <Wt/WComboBox.h>
#include <Wt/WFormModel.h>
#include <Wt/WLineEdit.h>
#include <Wt/WRadioButton.h>
#include <Wt/WPushButton.h>
#include <Wt/WStackedWidget.h>
#include <Wt/WTemplateFormView.h>
#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<Database::TrackListId>;
ReplaceTrackListModel()
{
addField(NameField);
setValidator(NameField, createMandatoryValidator());
}
Database::TrackListId getTrackListId() const
{
auto row {trackListModel->getRowFromString(valueText(NameField))};
return trackListModel->getValue(*row);
}
static
std::shared_ptr<TrackListModel>
createTrackListModel()
{
using namespace Database;
auto model {std::make_shared<TrackListModel>()};
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> trackListModel {createTrackListModel()};
};
}
PlayQueue::PlayQueue()
: Template {Wt::WString::tr("Lms.PlayQueue.template")}
{
@@ -55,6 +127,12 @@ PlayQueue::PlayQueue()
clearTracks();
});
Wt::WPushButton* saveBtn {bindNew<Wt::WPushButton>("save-btn", Wt::WString::tr("Lms.PlayQueue.template.save-btn"), Wt::TextFormat::XHTML)};
saveBtn->clicked().connect([=]
{
saveAsTrackList();
});
_entriesContainer = bindNew<InfiniteScrollingContainer>("entries");
_entriesContainer->onRequestElements.connect([this]
{
@@ -108,7 +186,7 @@ PlayQueue::PlayQueue()
_radioBtn->setCheckState(Wt::CheckState::Checked);
}
_nbTracks = bindNew<Wt::WText>("nb-tracks");
_nbTracks = bindNew<Wt::WText>("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<unsigned>(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<Template>(Wt::WString::tr("Lms.PlayQueue.template.save-as-tracklist"))};
modal->addFunction("id", &Wt::WTemplate::Functions::id);
modal->addFunction("tr", &Wt::WTemplate::Functions::tr);
Wt::WWidget* modalPtr {modal.get()};
auto* cancelBtn {modal->bindNew<Wt::WPushButton>("cancel-btn", Wt::WString::tr("Lms.cancel"))};
cancelBtn->clicked().connect([=]
{
LmsApp->getModalManager().dispose(modalPtr);
});
Wt::WStackedWidget* contentStack {modal->bindNew<Wt::WStackedWidget>("contents")};
// Create/Replace selector
enum Index : int
{
CreateTrackList = 0,
ReplaceTrackList = 1,
};
{
modal->bindNew<Wt::WRadioButton>("replace-tracklist-btn")
->clicked().connect([=]
{
contentStack->setCurrentIndex(Index::ReplaceTrackList);
});
Wt::WRadioButton* createTrackListBtn {modal->bindNew<Wt::WRadioButton>("create-tracklist-btn")};
createTrackListBtn->setChecked();
createTrackListBtn->clicked().connect([=]
{
contentStack->setCurrentIndex(Index::CreateTrackList);
});
}
// Create TrackList
Wt::WTemplateFormView* createTrackList {contentStack->addNew<Wt::WTemplateFormView>(Wt::WString::tr("Lms.PlayQueue.template.save-as-tracklist.create-tracklist"))};
auto createTrackListModel {std::make_shared<CreateTrackListModel>()};
createTrackList->setFormWidget(CreateTrackListModel::NameField, std::make_unique<Wt::WLineEdit>());
createTrackList->updateView(createTrackListModel.get());
// Replace TrackList
Wt::WTemplateFormView* replaceTrackList {contentStack->addNew<Wt::WTemplateFormView>(Wt::WString::tr("Lms.PlayQueue.template.save-as-tracklist.replace-tracklist"))};
auto replaceTrackListModel {std::make_shared<ReplaceTrackListModel>()};
{
auto name {std::make_unique<Wt::WComboBox>()};
name->setModel(replaceTrackListModel->trackListModel);
replaceTrackList->setFormWidget(ReplaceTrackListModel::NameField, std::move(name));
}
replaceTrackList->updateView(replaceTrackListModel.get());
auto* saveBtn {modal->bindNew<Wt::WPushButton>("save-btn", Wt::WString::tr("Lms.save"))};
saveBtn->clicked().connect([=]
{
bool success {};
switch (contentStack->currentIndex())
{
case Index::CreateTrackList:
createTrackList->updateModel(createTrackListModel.get());
if (createTrackListModel->validate())
{
exportToNewTrackList(createTrackListModel->getName());
success = true;
}
createTrackList->updateView(createTrackListModel.get());
break;
case Index::ReplaceTrackList:
replaceTrackList->updateModel(replaceTrackListModel.get());
if (replaceTrackListModel->validate())
{
exportToTrackList(replaceTrackListModel->getTrackListId());
success = true;
}
replaceTrackList->updateView(replaceTrackListModel.get());
break;
}
if (success)
LmsApp->getModalManager().dispose(modalPtr);
});
LmsApp->getModalManager().show(std::move(modal));
}
void
PlayQueue::exportToNewTrackList(const Wt::WString& name)
{
using namespace Database;
Database::TrackListId trackListId;
{
Session& session {LmsApp->getDbSession()};
auto transaction {session.createUniqueTransaction()};
TrackList::pointer trackList {session.create<TrackList>(name.toUTF8(), TrackListType::Playlist, false, LmsApp->getUser())};
trackListId = trackList->getId();
}
exportToTrackList(trackListId);
}
void
PlayQueue::exportToTrackList(Database::TrackListId trackListId)
{
using namespace Database;
Session& session {LmsApp->getDbSession()};
auto transaction {session.createUniqueTransaction()};
TrackList::pointer trackList {TrackList::find(LmsApp->getDbSession(), trackListId)};
trackList.modify()->clear();
Track::FindParameters params;
params.setTrackList(_tracklistId);
params.setDistinct(false);
params.setSortMethod(TrackSortMethod::TrackList);
const auto tracks {Track::find(session, params)};
for (const TrackId trackId : tracks.results)
session.create<TrackListEntry>(Track::find(session, trackId), trackList);
}
} // namespace UserInterface
+4
View File
@@ -89,6 +89,10 @@ class PlayQueue : public Template
void addRadioTrackFromSimilarity(std::shared_ptr<Similarity::Finder> similarityFinder);
void addRadioTrackFromClusters();
std::optional<float> getReplayGain(std::size_t pos, const Database::ObjectPtr<Database::Track>& track) const;
void saveAsTrackList();
void exportToNewTrackList(const Wt::WString& name);
void exportToTrackList(Database::TrackListId trackList);
static inline constexpr std::size_t _capacity {1000};
static inline constexpr std::size_t _batchSize {12};
+1 -1
View File
@@ -223,6 +223,7 @@ getTrackListTracks(Database::Session& session, Database::TrackListId trackListId
params.setClusters(clusters);
params.setRange({0, maxTrackCount});
params.setSortMethod(TrackSortMethod::TrackList);
params.setDistinct(false);
return Database::Track::find(session, params).results;
}
@@ -252,6 +253,5 @@ Explore::handleTracksAction(PlayQueueAction action, const std::vector<Database::
tracksAction.emit(action, tracksId);
}
} // namespace UserInterface
+3 -18
View File
@@ -94,7 +94,8 @@ namespace UserInterface
bindString("name", std::string {trackList->getName()}, Wt::TextFormat::Plain);
bindString("duration", Utils::durationToString(trackList->getDuration()));
bindString("track-count", Wt::WString::trn("Lms.Explore.TrackList.track-count", trackList->getCount()).arg(trackList->getCount()));
const auto trackCount {trackList->getCount()};
bindString("track-count", Wt::WString::trn("Lms.track-count", trackCount).arg(trackCount));
Wt::WContainerWidget* clusterContainers {bindNew<Wt::WContainerWidget>("clusters")};
{
@@ -189,6 +190,7 @@ namespace UserInterface
params.setTrackList(_trackListId);
params.setSortMethod(Database::TrackSortMethod::TrackList);
params.setRange({static_cast<std::size_t>(_container->getCount()), _batchSize});
params.setDistinct(false);
const auto trackIds {Database::Track::find(LmsApp->getDbSession(), params)};
for (const TrackId trackId : trackIds.results)
@@ -199,22 +201,5 @@ namespace UserInterface
_container->setHasMore(trackIds.moreResults && _container->getCount() < _maxCount);
}
/*
void
Tracks::addSome()
{
}
std::vector<Database::TrackId>
Tracks::getAllTracks()
{
RangeResults<TrackId> trackIds {_trackCollector.get(Range {})};
return std::move(trackIds.results);
}
*/
} // namespace UserInterface