Split the search view in order to workaround the scroll visibility issue

This commit is contained in:
emeric
2023-11-03 09:12:19 +01:00
parent e339a42883
commit 9ff1b215c2
3 changed files with 207 additions and 186 deletions
+22 -22
View File
@@ -3,29 +3,29 @@
<!--FORMS message blocks--> <!--FORMS message blocks-->
<message id="Lms.Explore.Search.template"> <message id="Lms.Explore.Search.template">
<ul class="nav nav-pills mb-3" id="myTab" role="tablist"> <div class="mb-3">
<li class="nav-item" role="presentation"> <ul class="nav nav-pills mb-3">
<button class="nav-link active" id="releases-tab" data-bs-toggle="tab" data-bs-target="#releases" type="button" role="tab" aria-controls="releases" aria-selected="true">${tr:Lms.Explore.releases}</button> <li class="nav-item">
</li> ${releases class="nav-link"}
<li class="nav-item" role="presentation"> </li>
<button class="nav-link" id="artists-tab" data-bs-toggle="tab" data-bs-target="#artists" type="button" role="tab" aria-controls="artists" aria-selected="false">${tr:Lms.Explore.artists}</button> <li class="nav-item">
</li> ${artists class="nav-link"}
<li class="nav-item" role="presentation"> </li>
<button class="nav-link" id="tracks-tab" data-bs-toggle="tab" data-bs-target="#tracks" type="button" role="tab" aria-controls="tracks" aria-selected="false">${tr:Lms.Explore.tracks}</button> <li class="nav-item">
</li> ${tracks class="nav-link"}
</ul> </li>
<div class="tab-content" id="myTabContent"> </ul>
<div class="tab-pane show active" id="releases" role="tabpanel" aria-labelledby="releases-tab">${releases}</div>
<div class="tab-pane" id="artists" role="tabpanel" aria-labelledby="artists-tab">
<div class="row mb-3">
<div class="col-lg-3">
${link-type class="form-select"}
</div>
</div>
${artists}
</div>
<div class="tab-pane" id="tracks" role="tabpanel" aria-labelledby="tracks-tab">${tracks}</div>
</div> </div>
${results}
</message>
<message id="Lms.Explore.Search.template.artists">
<div class="row mb-3">
<div class="col-lg-3">
${link-type class="form-select"}
</div>
</div>
${artists}
</message> </message>
</messages> </messages>
+137 -115
View File
@@ -19,6 +19,8 @@
#include "SearchView.hpp" #include "SearchView.hpp"
#include <Wt/WPushButton.h>
#include "services/database/Artist.hpp" #include "services/database/Artist.hpp"
#include "services/database/Release.hpp" #include "services/database/Release.hpp"
#include "services/database/Session.hpp" #include "services/database/Session.hpp"
@@ -35,142 +37,162 @@ using namespace Database;
namespace UserInterface namespace UserInterface
{ {
SearchView::SearchView(Filters& filters, PlayQueueController& playQueueController) SearchView::SearchView(Filters& filters, PlayQueueController& playQueueController)
: Wt::WTemplate {Wt::WString::tr("Lms.Explore.Search.template")} : Wt::WTemplate{ Wt::WString::tr("Lms.Explore.Search.template") }
, _playQueueController {playQueueController} , _playQueueController{ playQueueController }
, _filters {filters} , _filters{ filters }
, _artistCollector {filters, ArtistCollector::Mode::Search, getMaxCount(Mode::Artist)} , _artistCollector{ filters, ArtistCollector::Mode::Search, getMaxCount(Mode::Artist) }
, _releaseCollector {filters, ReleaseCollector::Mode::Search, getMaxCount(Mode::Release)} , _releaseCollector{ filters, ReleaseCollector::Mode::Search, getMaxCount(Mode::Release) }
, _trackCollector {filters, TrackCollector::Mode::Search, getMaxCount(Mode::Track)} , _trackCollector{ filters, TrackCollector::Mode::Search, getMaxCount(Mode::Track) }
{ {
addFunction("tr", &Wt::WTemplate::Functions::tr); addFunction("tr", &Wt::WTemplate::Functions::tr);
_artists = bindNew<InfiniteScrollingContainer>("artists", Wt::WString::tr("Lms.Explore.Artists.template.container")); _stack = bindNew<Wt::WStackedWidget>("results");
_artists->onRequestElements.connect([this] { addSomeArtists(); });
_artistLinkType = bindNew<Wt::WComboBox>("link-type"); // releases
_artistLinkType->setModel(ArtistListHelpers::createArtistLinkTypesModel()); _releases = _stack->addNew<InfiniteScrollingContainer>(Wt::WString::tr("Lms.Explore.Releases.template.container"));
_artistLinkType->changed().connect([this] _releases->onRequestElements.connect([this] { addSomeReleases(); });
{
const std::optional<TrackArtistLinkType> linkType {static_cast<ArtistLinkTypesModel*>(_artistLinkType->model().get())->getValue(_artistLinkType->currentIndex())};
refreshView(linkType);
});
_releases = bindNew<InfiniteScrollingContainer>("releases", Wt::WString::tr("Lms.Explore.Releases.template.container")); // artists
_releases->onRequestElements.connect([this] { addSomeReleases(); }); {
Wt::WTemplate* artistResults{ _stack->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Search.template.artists")) };
_tracks = bindNew<InfiniteScrollingContainer>("tracks"); _artistLinkType = artistResults->bindNew<Wt::WComboBox>("link-type");
_tracks->onRequestElements.connect([this] { addSomeTracks(); }); _artistLinkType->setModel(ArtistListHelpers::createArtistLinkTypesModel());
_artistLinkType->changed().connect([this]
{
const std::optional<TrackArtistLinkType> linkType{ static_cast<ArtistLinkTypesModel*>(_artistLinkType->model().get())->getValue(_artistLinkType->currentIndex()) };
refreshView(linkType);
});
filters.updated().connect([=] _artists = artistResults->bindNew<InfiniteScrollingContainer>("artists", Wt::WString::tr("Lms.Explore.Artists.template.container"));
{ _artists->onRequestElements.connect([this] { addSomeArtists(); });
refreshView(); }
});
LmsApp->getScannerEvents().scanComplete.connect(this, [this](const Scanner::ScanStats& stats) // Tracks
{ _tracks = _stack->addNew<InfiniteScrollingContainer>();
if (stats.nbChanges()) _tracks->onRequestElements.connect([this] { addSomeTracks(); });
_artistLinkType->setModel(ArtistListHelpers::createArtistLinkTypesModel());
});
}
std::size_t // Menu
SearchView::getBatchSize(Mode mode) const auto bindMenuItem{ [this](std::size_t index, const std::string& var, const Wt::WString& title)
{ {
auto it {_batchSizes.find(mode)}; Wt::WPushButton* menuItem {bindNew<Wt::WPushButton>(var, title)};
assert(it != _batchSizes.cend()); menuItem->clicked().connect([=]
return it->second; {
} _stack->setCurrentIndex(index);
_currentActiveItem->removeStyleClass("active");
menuItem->addStyleClass("active");
_currentActiveItem = menuItem;
});
std::size_t if (index == 0)
SearchView::getMaxCount(Mode mode) const {
{ _currentActiveItem = menuItem;
auto it {_maxCounts.find(mode)}; _currentActiveItem->addStyleClass("active");
assert(it != _maxCounts.cend()); }
return it->second; } };
}
void bindMenuItem(0, "releases", Wt::WString::tr("Lms.Explore.releases"));
SearchView::refreshView(std::optional<TrackArtistLinkType> linkType) bindMenuItem(1, "artists", Wt::WString::tr("Lms.Explore.artists"));
{ bindMenuItem(2, "tracks", Wt::WString::tr("Lms.Explore.tracks"));
_artistCollector.setArtistLinkType(linkType);
refreshView();
}
void filters.updated().connect([=]
SearchView::refreshView(const Wt::WString& searchText) {
{ refreshView();
_releaseCollector.setSearch(searchText.toUTF8()); });
_artistCollector.setSearch(searchText.toUTF8());
_trackCollector.setSearch(searchText.toUTF8());
refreshView();
}
void LmsApp->getScannerEvents().scanComplete.connect(this, [this](const Scanner::ScanStats& stats)
SearchView::refreshView() {
{ if (stats.nbChanges())
_artists->reset(); _artistLinkType->setModel(ArtistListHelpers::createArtistLinkTypesModel());
_releases->reset(); });
_tracks->reset(); }
addSomeArtists(); std::size_t SearchView::getBatchSize(Mode mode) const
addSomeReleases(); {
addSomeTracks(); auto it{ _batchSizes.find(mode) };
} assert(it != _batchSizes.cend());
return it->second;
}
void std::size_t SearchView::getMaxCount(Mode mode) const
SearchView::addSomeArtists() {
{ auto it{ _maxCounts.find(mode) };
using namespace Database; assert(it != _maxCounts.cend());
return it->second;
}
const Range range {_artists->getCount(), getBatchSize(Mode::Artist)}; void SearchView::refreshView(std::optional<TrackArtistLinkType> linkType)
const RangeResults<ArtistId> artistIds {_artistCollector.get(range)}; {
{ _artistCollector.setArtistLinkType(linkType);
auto transaction {LmsApp->getDbSession().createSharedTransaction()}; refreshView();
}
for (const ArtistId artistId : artistIds.results) void SearchView::refreshView(const Wt::WString& searchText)
{ {
const Artist::pointer artist {Artist::find(LmsApp->getDbSession(), artistId)}; _releaseCollector.setSearch(searchText.toUTF8());
_artists->add(ArtistListHelpers::createEntry(artist)); _artistCollector.setSearch(searchText.toUTF8());
} _trackCollector.setSearch(searchText.toUTF8());
} refreshView();
} }
void void SearchView::refreshView()
SearchView::addSomeReleases() {
{ _artists->reset();
using namespace Database; _releases->reset();
_tracks->reset();
}
const Range range {_releases->getCount(), getBatchSize(Mode::Release)}; void SearchView::addSomeArtists()
const RangeResults<ReleaseId> releaseIds {_releaseCollector.get(range)}; {
{ using namespace Database;
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
for (const ReleaseId releaseId : releaseIds.results) const Range range{ _artists->getCount(), getBatchSize(Mode::Artist) };
{ const RangeResults<ArtistId> artistIds{ _artistCollector.get(range) };
const Release::pointer release {Release::find(LmsApp->getDbSession(), releaseId)}; {
_releases->add(ReleaseListHelpers::createEntry(release)); auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
}
}
}
void for (const ArtistId artistId : artistIds.results)
SearchView::addSomeTracks() {
{ const Artist::pointer artist{ Artist::find(LmsApp->getDbSession(), artistId) };
using namespace Database; _artists->add(ArtistListHelpers::createEntry(artist));
}
}
}
const Range range {_tracks->getCount(), getBatchSize(Mode::Track)}; void SearchView::addSomeReleases()
const RangeResults<TrackId> trackIds {_trackCollector.get(range)}; {
using namespace Database;
{ const Range range{ _releases->getCount(), getBatchSize(Mode::Release) };
auto transaction {LmsApp->getDbSession().createSharedTransaction()}; const RangeResults<ReleaseId> releaseIds{ _releaseCollector.get(range) };
{
auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
for (const TrackId trackId : trackIds.results) for (const ReleaseId releaseId : releaseIds.results)
{ {
const Track::pointer track {Track::find(LmsApp->getDbSession(), trackId)}; const Release::pointer release{ Release::find(LmsApp->getDbSession(), releaseId) };
_tracks->add(TrackListHelpers::createEntry(track, _playQueueController, _filters)); _releases->add(ReleaseListHelpers::createEntry(release));
} }
} }
} }
void SearchView::addSomeTracks()
{
using namespace Database;
const Range range{ _tracks->getCount(), getBatchSize(Mode::Track) };
const RangeResults<TrackId> trackIds{ _trackCollector.get(range) };
{
auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
for (const TrackId trackId : trackIds.results)
{
const Track::pointer track{ Track::find(LmsApp->getDbSession(), trackId) };
_tracks->add(TrackListHelpers::createEntry(track, _playQueueController, _filters));
}
}
}
} // namespace UserInterface } // namespace UserInterface
+48 -49
View File
@@ -33,62 +33,61 @@
namespace UserInterface namespace UserInterface
{ {
class InfiniteScrollingContainer; class InfiniteScrollingContainer;
class Filters; class Filters;
class PlayQueueController; class PlayQueueController;
class SearchView : public Wt::WTemplate class SearchView : public Wt::WTemplate
{ {
public: public:
SearchView(Filters& filters, PlayQueueController& playQueueController); SearchView(Filters& filters, PlayQueueController& playQueueController);
void refreshView(const Wt::WString& searchText); void refreshView(const Wt::WString& searchText);
private: private:
// same order as in the menu // same order as in the menu
enum class Mode enum class Mode
{ {
Artist, Artist,
Release, Release,
Track, Track,
}; };
static constexpr Mode _defaultMode {Mode::Release}; static constexpr Mode _defaultMode{ Mode::Release };
static inline std::unordered_map<Mode, std::size_t> _batchSizes static inline std::unordered_map<Mode, std::size_t> _batchSizes
{ {
{Mode::Artist, 6}, {Mode::Artist, 6},
{Mode::Release, 6}, {Mode::Release, 6},
{Mode::Track, 6}, {Mode::Track, 6},
}; };
static inline std::unordered_map<Mode, std::size_t> _maxCounts static inline std::unordered_map<Mode, std::size_t> _maxCounts
{ {
{Mode::Artist, 1000}, {Mode::Artist, 1000},
{Mode::Release, 504}, {Mode::Release, 504},
{Mode::Track, 500}, {Mode::Track, 500},
}; };
std::size_t getBatchSize(Mode mode) const; std::size_t getBatchSize(Mode mode) const;
std::size_t getMaxCount(Mode mode) const; std::size_t getMaxCount(Mode mode) const;
void refreshView(); void refreshView();
void refreshView(std::optional<Database::TrackArtistLinkType> linkType); void refreshView(std::optional<Database::TrackArtistLinkType> linkType);
void addSomeArtists(); void addSomeArtists();
void addSomeReleases(); void addSomeReleases();
void addSomeTracks(); void addSomeTracks();
PlayQueueController& _playQueueController; PlayQueueController& _playQueueController;
Filters& _filters; Filters& _filters;
ArtistCollector _artistCollector; ArtistCollector _artistCollector;
ReleaseCollector _releaseCollector; ReleaseCollector _releaseCollector;
TrackCollector _trackCollector; TrackCollector _trackCollector;
Wt::WStackedWidget* _stack{};
Wt::WWidget* _currentActiveItem {};
InfiniteScrollingContainer* _artists{};
InfiniteScrollingContainer* _releases{};
InfiniteScrollingContainer* _tracks{};
InfiniteScrollingContainer* _artists {}; Wt::WComboBox* _artistLinkType{};
InfiniteScrollingContainer* _releases {}; };
InfiniteScrollingContainer* _tracks {};
Wt::WComboBox* _artistLinkType {};
std::vector<InfiniteScrollingContainer*> _results;
};
} // namespace UserInterface } // namespace UserInterface