Added a way to save the current playqueue in a playlist
This commit is contained in:
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user