Added basic UI to display shared playlists, ref #391

This commit is contained in:
emeric
2024-12-18 20:20:39 +01:00
parent d49d718922
commit 5c73b5bb97
13 changed files with 154 additions and 48 deletions
+2
View File
@@ -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>
+2
View File
@@ -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>
+2
View File
@@ -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>
+2
View File
@@ -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>
+2
View File
@@ -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>
+2
View File
@@ -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
View File
@@ -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">
+7
View File
@@ -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;
+4 -4
View File
@@ -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());
});
{
+11
View File
@@ -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] {
+50 -12
View File
@@ -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;
}
+12 -3
View File
@@ -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{};