Added basic UI to display shared playlists, ref #391
This commit is contained in:
@@ -269,6 +269,8 @@
|
||||
|
||||
<!--Explore:TrackLists-->
|
||||
<message id="Lms.Explore.TrackLists.del-tracklist-confirm">Delete playlist?</message>
|
||||
<message id="Lms.Explore.TrackLists.type-owned">My Playlists</message>
|
||||
<message id="Lms.Explore.TrackLists.type-shared">Shared playlists</message>
|
||||
|
||||
<!--Explore:Search-->
|
||||
<message id="Lms.Explore.Search.search-placeholder">Search...</message>
|
||||
|
||||
@@ -269,6 +269,8 @@
|
||||
|
||||
<!--Explore:TrackLists-->
|
||||
<message id="Lms.Explore.TrackLists.del-tracklist-confirm">Supprimer la liste de lecture ?</message>
|
||||
<message id="Lms.Explore.TrackLists.type-owned">Mes listes</message>
|
||||
<message id="Lms.Explore.TrackLists.type-shared">Listes partagées</message>
|
||||
|
||||
<!--Explore:Search-->
|
||||
<message id="Lms.Explore.Search.search-placeholder">Rechercher...</message>
|
||||
|
||||
@@ -269,6 +269,8 @@
|
||||
|
||||
<!--Explore:TrackLists-->
|
||||
<message id="Lms.Explore.TrackLists.del-tracklist-confirm">Eliminare la playlist?</message>
|
||||
<message id="Lms.Explore.TrackLists.type-owned">Le mie liste</message>
|
||||
<message id="Lms.Explore.TrackLists.type-shared">Liste condivise</message>
|
||||
|
||||
<!--Explore:Search-->
|
||||
<message id="Lms.Explore.Search.search-placeholder">Ricerca...</message>
|
||||
|
||||
@@ -295,6 +295,8 @@
|
||||
|
||||
<!--Explore:TrackLists-->
|
||||
<message id="Lms.Explore.TrackLists.del-tracklist-confirm">Usunąć listę odtwarzania?</message>
|
||||
<message id="Lms.Explore.TrackLists.type-owned">Moje listy</message>
|
||||
<message id="Lms.Explore.TrackLists.type-shared">Udostępnione listy</message>
|
||||
|
||||
<!--Explore:Search-->
|
||||
<message id="Lms.Explore.Search.search-placeholder">Szukaj...</message>
|
||||
|
||||
@@ -270,6 +270,8 @@
|
||||
<!--Explore:TrackLists-->
|
||||
<message id="Lms.Explore.TrackLists.del-tracklist-confirm">删除播放列表?</message>
|
||||
|
||||
|
||||
|
||||
<!--Explore:Search-->
|
||||
<message id="Lms.Explore.Search.search-placeholder">搜索...</message>
|
||||
|
||||
|
||||
@@ -14,7 +14,9 @@
|
||||
<li>${play-shuffled class="dropdown-item"}</li>
|
||||
<li>${play-last class="dropdown-item"}</li>
|
||||
<li>${download class="dropdown-item"}</li>
|
||||
${<if-has-delete>}
|
||||
<li>${delete class="dropdown-item"}</li>
|
||||
${</if-has-delete>}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+17
-1
@@ -3,12 +3,28 @@
|
||||
<messages xmlns:if="Wt.WTemplate.conditions">
|
||||
|
||||
<message id="Lms.Explore.TrackLists.template">
|
||||
<div class="d-flex flex-row-reverse">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
${tracklist-type class="me-1"}
|
||||
<div class="d-flex">
|
||||
${search class="form-control form-control-sm me-1" type="search"}
|
||||
${sort-mode}
|
||||
</div>
|
||||
</div>
|
||||
${tracklists}
|
||||
</message>
|
||||
|
||||
<message id="Lms.Explore.TrackLists.template.type-selector">
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-sm btn-outline-primary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
${selected-item}
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li>${owned class="dropdown-item"}</li>
|
||||
<li>${shared class="dropdown-item"}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</message>
|
||||
|
||||
<message id="Lms.Explore.TrackLists.template.sort-mode">
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-sm btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -63,9 +63,11 @@ namespace lms::db
|
||||
struct FindParameters
|
||||
{
|
||||
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,6 +140,9 @@ namespace lms::ui
|
||||
bindNew<Wt::WPushButton>("download", Wt::WString::tr("Lms.Explore.download"))
|
||||
->setLink(Wt::WLink{ std::make_unique<DownloadTrackListResource>(*trackListId) });
|
||||
|
||||
if (trackList->getUserId() == LmsApp->getUserId())
|
||||
{
|
||||
setCondition("if-has-delete", true);
|
||||
bindNew<Wt::WPushButton>("delete", Wt::WString::tr("Lms.delete"))
|
||||
->clicked()
|
||||
.connect([this, trackListId] {
|
||||
@@ -164,6 +174,7 @@ namespace lms::ui
|
||||
|
||||
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