Added basic UI to display shared playlists, ref #391
This commit is contained in:
@@ -38,6 +38,8 @@ namespace lms::db
|
||||
template<typename ResultType>
|
||||
Wt::Dbo::Query<ResultType> createQuery(Session& session, std::string_view itemToSelect, const TrackList::FindParameters& params)
|
||||
{
|
||||
assert(!params.user.isValid() || !params.excludedUser.isValid());
|
||||
|
||||
auto query{ session.getDboSession()->query<ResultType>("SELECT " + std::string{ itemToSelect } + " FROM tracklist t_l") };
|
||||
|
||||
if (!params.clusters.empty() || params.mediaLibrary.isValid())
|
||||
@@ -46,6 +48,9 @@ namespace lms::db
|
||||
query.groupBy("t_l.id");
|
||||
}
|
||||
|
||||
for (std::string_view keyword : params.keywords)
|
||||
query.where("t_l.name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'").bind("%" + utils::escapeLikeKeyword(keyword) + "%");
|
||||
|
||||
if (params.mediaLibrary.isValid())
|
||||
query.join("track t ON t.id = t_l_e.track_id");
|
||||
|
||||
@@ -54,6 +59,8 @@ namespace lms::db
|
||||
|
||||
if (params.user.isValid())
|
||||
query.where("t_l.user_id = ?").bind(params.user);
|
||||
else if (params.excludedUser.isValid())
|
||||
query.where("t_l.user_id <> ? OR t_l.user_id IS NULL").bind(params.excludedUser);
|
||||
|
||||
if (params.type)
|
||||
query.where("t_l.type = ?").bind(*params.type);
|
||||
|
||||
@@ -62,10 +62,12 @@ namespace lms::db
|
||||
// Search utility
|
||||
struct FindParameters
|
||||
{
|
||||
std::vector<ClusterId> clusters; // if non empty, tracklists that have tracks that belong to these clusters
|
||||
std::vector<ClusterId> clusters; // if non empty, tracklists that have tracks that belong to these clusters
|
||||
std::vector<std::string_view> keywords; // if non empty, name must match all of these keywords (on either name field OR sort name field)
|
||||
std::optional<Range> range;
|
||||
std::optional<TrackListType> type;
|
||||
UserId user; // only tracklists owned by this user
|
||||
UserId excludedUser; // only tracklists *not* owned by this user
|
||||
MediaLibraryId mediaLibrary; // only tracklists that have songs in this media library
|
||||
TrackListSortMethod sortMethod{ TrackListSortMethod::None };
|
||||
std::optional<Visibility> visibility;
|
||||
@@ -75,6 +77,11 @@ namespace lms::db
|
||||
clusters.assign(std::cbegin(_clusters), std::cend(_clusters));
|
||||
return *this;
|
||||
}
|
||||
FindParameters& setKeywords(const std::vector<std::string_view>& _keywords)
|
||||
{
|
||||
keywords = _keywords;
|
||||
return *this;
|
||||
}
|
||||
FindParameters& setRange(std::optional<Range> _range)
|
||||
{
|
||||
range = _range;
|
||||
@@ -90,6 +97,12 @@ namespace lms::db
|
||||
user = _user;
|
||||
return *this;
|
||||
}
|
||||
FindParameters& setExcludedUser(UserId _user)
|
||||
{
|
||||
excludedUser = _user;
|
||||
return *this;
|
||||
}
|
||||
|
||||
FindParameters& setMediaLibrary(MediaLibraryId _mediaLibrary)
|
||||
{
|
||||
mediaLibrary = _mediaLibrary;
|
||||
|
||||
@@ -45,10 +45,10 @@ namespace lms::ui
|
||||
addFunction("tr", &Wt::WTemplate::Functions::tr);
|
||||
addFunction("id", &Wt::WTemplate::Functions::id);
|
||||
|
||||
Wt::WLineEdit* searEdit{ bindNew<Wt::WLineEdit>("search") };
|
||||
searEdit->setPlaceholderText(Wt::WString::tr("Lms.Explore.Search.search-placeholder"));
|
||||
searEdit->textInput().connect([this, searEdit] {
|
||||
refreshView(searEdit->text());
|
||||
Wt::WLineEdit* searchEdit{ bindNew<Wt::WLineEdit>("search") };
|
||||
searchEdit->setPlaceholderText(Wt::WString::tr("Lms.Explore.Search.search-placeholder"));
|
||||
searchEdit->textInput().connect([this, searchEdit] {
|
||||
refreshView(searchEdit->text());
|
||||
});
|
||||
|
||||
{
|
||||
|
||||
@@ -84,6 +84,13 @@ namespace lms::ui
|
||||
if (!trackList)
|
||||
throw TrackListNotFoundException{};
|
||||
|
||||
if (trackList->getUserId().isValid()
|
||||
&& LmsApp->getUserId() != trackList->getUserId()
|
||||
&& trackList->getVisibility() != db::TrackList::Visibility::Public)
|
||||
{
|
||||
throw TrackListNotFoundException{};
|
||||
}
|
||||
|
||||
LmsApp->setTitle(std::string{ trackList->getName() });
|
||||
_trackListId = *trackListId;
|
||||
|
||||
@@ -133,37 +140,41 @@ namespace lms::ui
|
||||
bindNew<Wt::WPushButton>("download", Wt::WString::tr("Lms.Explore.download"))
|
||||
->setLink(Wt::WLink{ std::make_unique<DownloadTrackListResource>(*trackListId) });
|
||||
|
||||
bindNew<Wt::WPushButton>("delete", Wt::WString::tr("Lms.delete"))
|
||||
->clicked()
|
||||
.connect([this, trackListId] {
|
||||
auto modal{ std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.TrackList.template.delete-tracklist")) };
|
||||
modal->addFunction("tr", &Wt::WTemplate::Functions::tr);
|
||||
Wt::WWidget* modalPtr{ modal.get() };
|
||||
if (trackList->getUserId() == LmsApp->getUserId())
|
||||
{
|
||||
setCondition("if-has-delete", true);
|
||||
bindNew<Wt::WPushButton>("delete", Wt::WString::tr("Lms.delete"))
|
||||
->clicked()
|
||||
.connect([this, trackListId] {
|
||||
auto modal{ std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.TrackList.template.delete-tracklist")) };
|
||||
modal->addFunction("tr", &Wt::WTemplate::Functions::tr);
|
||||
Wt::WWidget* modalPtr{ modal.get() };
|
||||
|
||||
auto* delBtn{ modal->bindNew<Wt::WPushButton>("del-btn", Wt::WString::tr("Lms.delete")) };
|
||||
delBtn->clicked().connect([this, trackListId, modalPtr] {
|
||||
{
|
||||
auto transaction{ LmsApp->getDbSession().createWriteTransaction() };
|
||||
auto* delBtn{ modal->bindNew<Wt::WPushButton>("del-btn", Wt::WString::tr("Lms.delete")) };
|
||||
delBtn->clicked().connect([this, trackListId, modalPtr] {
|
||||
{
|
||||
auto transaction{ LmsApp->getDbSession().createWriteTransaction() };
|
||||
|
||||
db::TrackList::pointer trackList{ db::TrackList::find(LmsApp->getDbSession(), *trackListId) };
|
||||
if (trackList)
|
||||
trackList.remove();
|
||||
}
|
||||
db::TrackList::pointer trackList{ db::TrackList::find(LmsApp->getDbSession(), *trackListId) };
|
||||
if (trackList)
|
||||
trackList.remove();
|
||||
}
|
||||
|
||||
clear();
|
||||
trackListDeleted.emit(*trackListId);
|
||||
LmsApp->setInternalPath("/tracklists", true);
|
||||
clear();
|
||||
trackListDeleted.emit(*trackListId);
|
||||
LmsApp->setInternalPath("/tracklists", true);
|
||||
|
||||
LmsApp->getModalManager().dispose(modalPtr);
|
||||
LmsApp->getModalManager().dispose(modalPtr);
|
||||
});
|
||||
|
||||
auto* cancelBtn{ modal->bindNew<Wt::WPushButton>("cancel-btn", Wt::WString::tr("Lms.cancel")) };
|
||||
cancelBtn->clicked().connect([=] {
|
||||
LmsApp->getModalManager().dispose(modalPtr);
|
||||
});
|
||||
|
||||
LmsApp->getModalManager().show(std::move(modal));
|
||||
});
|
||||
|
||||
auto* cancelBtn{ modal->bindNew<Wt::WPushButton>("cancel-btn", Wt::WString::tr("Lms.cancel")) };
|
||||
cancelBtn->clicked().connect([=] {
|
||||
LmsApp->getModalManager().dispose(modalPtr);
|
||||
});
|
||||
|
||||
LmsApp->getModalManager().show(std::move(modal));
|
||||
});
|
||||
}
|
||||
|
||||
_container = bindNew<InfiniteScrollingContainer>("tracks", Wt::WString::tr("Lms.Explore.TrackList.template.entry-container"));
|
||||
_container->onRequestElements.connect([this] {
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include "TrackListsView.hpp"
|
||||
|
||||
#include <Wt/WLineEdit.h>
|
||||
#include <Wt/WPushButton.h>
|
||||
|
||||
#include "database/Session.hpp"
|
||||
@@ -44,16 +45,38 @@ namespace lms::ui
|
||||
addFunction("id", &Wt::WTemplate::Functions::id);
|
||||
|
||||
{
|
||||
_mode = state::readValue<Mode>("tracklists_sort_mode").value_or(_defaultMode);
|
||||
_type = state::readValue<Type>("tracklists_type").value_or(_defaultType);
|
||||
|
||||
using SortModeSelector = DropDownMenuSelector<TrackLists::Mode>;
|
||||
SortModeSelector* sortModeSelector{ bindNew<SortModeSelector>("sort-mode", Wt::WString::tr("Lms.Explore.TrackLists.template.sort-mode"), _mode) };
|
||||
sortModeSelector->bindItem("recently-modified", Wt::WString::tr("Lms.Explore.recently-modified"), Mode::RecentlyModified);
|
||||
sortModeSelector->bindItem("all", Wt::WString::tr("Lms.Explore.all"), Mode::All);
|
||||
using TypeSelector = DropDownMenuSelector<Type>;
|
||||
TypeSelector* typeSelector{ bindNew<TypeSelector>("tracklist-type", Wt::WString::tr("Lms.Explore.TrackLists.template.type-selector"), _type) };
|
||||
typeSelector->bindItem("owned", Wt::WString::tr("Lms.Explore.TrackLists.type-owned"), Type::Owned);
|
||||
typeSelector->bindItem("shared", Wt::WString::tr("Lms.Explore.TrackLists.type-shared"), Type::Shared);
|
||||
|
||||
sortModeSelector->itemSelected.connect(this, [this](TrackLists::Mode mode) {
|
||||
state::writeValue<Mode>("tracklists_sort_mode", mode);
|
||||
_mode = mode;
|
||||
typeSelector->itemSelected.connect(this, [this](Type type) {
|
||||
state::writeValue<Type>("tracklists_type", type);
|
||||
_type = type;
|
||||
refreshView();
|
||||
});
|
||||
}
|
||||
|
||||
Wt::WLineEdit* searchEdit{ bindNew<Wt::WLineEdit>("search") };
|
||||
searchEdit->setPlaceholderText(Wt::WString::tr("Lms.Explore.Search.search-placeholder"));
|
||||
searchEdit->textInput().connect([this, searchEdit] {
|
||||
_searchText = searchEdit->text().toUTF8();
|
||||
refreshView();
|
||||
});
|
||||
|
||||
{
|
||||
_sortMode = state::readValue<SortMode>("tracklists_sort_mode").value_or(_defaultSortMode);
|
||||
|
||||
using SortModeSelector = DropDownMenuSelector<SortMode>;
|
||||
SortModeSelector* sortModeSelector{ bindNew<SortModeSelector>("sort-mode", Wt::WString::tr("Lms.Explore.TrackLists.template.sort-mode"), _sortMode) };
|
||||
sortModeSelector->bindItem("recently-modified", Wt::WString::tr("Lms.Explore.recently-modified"), SortMode::RecentlyModified);
|
||||
sortModeSelector->bindItem("all", Wt::WString::tr("Lms.Explore.all"), SortMode::All);
|
||||
|
||||
sortModeSelector->itemSelected.connect(this, [this](SortMode mode) {
|
||||
state::writeValue<SortMode>("tracklists_sort_mode", mode);
|
||||
_sortMode = mode;
|
||||
refreshView();
|
||||
});
|
||||
}
|
||||
@@ -94,17 +117,32 @@ namespace lms::ui
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
TrackList::FindParameters params;
|
||||
|
||||
if (!_searchText.empty())
|
||||
params.setKeywords(core::stringUtils::splitString(_searchText, ' '));
|
||||
params.setClusters(_filters.getClusters());
|
||||
params.setMediaLibrary(_filters.getMediaLibrary());
|
||||
params.setUser(LmsApp->getUserId());
|
||||
params.setType(TrackListType::PlayList);
|
||||
params.setRange(range);
|
||||
switch (_mode)
|
||||
|
||||
switch (_type)
|
||||
{
|
||||
case Mode::All:
|
||||
case Type::Owned:
|
||||
params.setUser(LmsApp->getUserId());
|
||||
break;
|
||||
|
||||
case Type::Shared:
|
||||
params.setExcludedUser(LmsApp->getUserId());
|
||||
params.setVisibility(db::TrackList::Visibility::Public);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (_sortMode)
|
||||
{
|
||||
case SortMode::All:
|
||||
params.setSortMethod(TrackListSortMethod::Name);
|
||||
break;
|
||||
case Mode::RecentlyModified:
|
||||
case SortMode::RecentlyModified:
|
||||
params.setSortMethod(TrackListSortMethod::LastModifiedDesc);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -46,21 +46,30 @@ namespace lms::ui
|
||||
void onTrackListDeleted(db::TrackListId trackListId);
|
||||
|
||||
private:
|
||||
enum class Mode
|
||||
enum class SortMode
|
||||
{
|
||||
RecentlyModified,
|
||||
All,
|
||||
};
|
||||
|
||||
enum class Type
|
||||
{
|
||||
Owned,
|
||||
Shared,
|
||||
};
|
||||
|
||||
void refreshView();
|
||||
void addSome();
|
||||
void addTracklist(const db::ObjectPtr<db::TrackList>& trackList);
|
||||
|
||||
static constexpr Mode _defaultMode{ Mode::RecentlyModified };
|
||||
static constexpr SortMode _defaultSortMode{ SortMode::RecentlyModified };
|
||||
static constexpr Type _defaultType{ Type::Owned };
|
||||
static constexpr std::size_t _batchSize{ 30 };
|
||||
static constexpr std::size_t _maxCount{ 500 };
|
||||
|
||||
Mode _mode;
|
||||
SortMode _sortMode{ _defaultSortMode };
|
||||
Type _type{ _defaultType };
|
||||
std::string _searchText;
|
||||
Filters& _filters;
|
||||
Wt::WWidget* _currentActiveItem{};
|
||||
InfiniteScrollingContainer* _container{};
|
||||
|
||||
Reference in New Issue
Block a user